This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed. ## Useful Resources - Matt Ashby Crime Mapping course: https://github.com/mpjashby/crimemapping/ - Spatial Modelling for Data Scientists: https://gdsl-ul.github.io/san/ - R for Data Science: https://r4ds.had.co.nz/index.html - Geocomputation with R: https://geocompr.robinlovelace.net/

Exploring Predictors of COVID Crime Shifts

In this notebook, I’ll be prediting crime trends in London by MSOA, and looking at where COVID has been most impactive. I’ll then try to find correlates.

Tasks

  1. Read all burglary and robbery data - 2018 onwards
  2. Assign to MSOA
  3. Predict trend by MSOA
  4. Identify error by MSOA
  5. Visualise and model
# Data manipulation, transformation and visualisation
library(tidyverse)
# Nice tables
library(kableExtra)
# Simple features (a standardised way to encode vector data ie. points, lines, polygons)
library(sf) 
# Spatial objects conversion
library(sp) 
# Thematic maps
library(tmap) 
# Colour palettes
library(RColorBrewer) 
# More colour palettes
library(viridis)

library(raster)  # raster data
library(rgdal)  # input/output, projections
library(rgeos)  # geometry ops
library(spdep)  # spatial dependence

library(Metrics)
library(caret)
Loading required package: lattice
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     

Attaching package: 㤼㸱caret㤼㸲

The following objects are masked from 㤼㸱package:Metrics㤼㸲:

    precision, recall

The following object is masked from 㤼㸱package:purrr㤼㸲:

    lift

One of the first things I notice is that while Python code is generally quite careful about imports, here we globally import everything…which is nice, but I’m also not quite clear which functions are coming from which libraries.

Now, let’s import all our crime data. Let’s start by one dataframe before figuring how to automate and concatenate. Notice R uses slashes that are the otehr way to Python and Windows

test_df <- read.csv("crimes/2018-01/2018-01-metropolitan-street.csv")
test_df
NA

Let’s look at all the unique crime types. Notice how we have a similar to the “unique” method in Python, and access a specific function using the same syntax.

unique(test_df["Crime.type"])

To avoid this getting particularly computationally intensive, let’s write a function to pull out robberies and burglaries, and assign them a specific MSOA. Then we can iterate over all our months and get monthly counts for each offence type.

subset_df <- filter(test_df, Crime.type=="Burglary" | Crime.type=="Robbery")
subset_df

Weirdly, here I don’t need quotation marks for my colum name…not sure what’s driving that. But it’s easy to select a subset by column, and use logical comparators.

With that in mind, let’s now read our MSOA borders and assign these to an MSOA. I’ve set the OSGB CRS code as it doesn’t seem to automatically assign it.

lsoa_borders <- st_read("msoa_borders/MSOA_2011_London_gen_MHW.tab", crs=27700)
Reading layer `MSOA_2011_London_gen_MHW' from data source `C:\Users\andre\Dropbox\Data Projects\Covid_crime_shift\msoa_borders\MSOA_2011_London_gen_MHW.tab' using driver `MapInfo File'
Simple feature collection with 983 features and 12 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 503574.2 ymin: 155850.8 xmax: 561956.7 ymax: 200933.6
projected CRS:  OSGB 1936 / British National Grid
lsoa_borders
Simple feature collection with 983 features and 12 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 503574.2 ymin: 155850.8 xmax: 561956.7 ymax: 200933.6
projected CRS:  OSGB 1936 / British National Grid
First 10 features:
    MSOA11CD                 MSOA11NM   LAD11CD              LAD11NM   RGN11CD RGN11NM UsualRes HholdRes ComEstRes PopDen Hholds AvHholdSz
1  E02000001       City of London 001 E09000001       City of London E12000007  London     7375     7187       188   25.5   4385       1.6
2  E02000002 Barking and Dagenham 001 E09000002 Barking and Dagenham E12000007  London     6775     6724        51   31.3   2713       2.5
3  E02000003 Barking and Dagenham 002 E09000002 Barking and Dagenham E12000007  London    10045    10033        12   46.9   3834       2.6
4  E02000004 Barking and Dagenham 003 E09000002 Barking and Dagenham E12000007  London     6182     5937       245   24.8   2318       2.6
5  E02000005 Barking and Dagenham 004 E09000002 Barking and Dagenham E12000007  London     8562     8562         0   72.1   3183       2.7
6  E02000007 Barking and Dagenham 006 E09000002 Barking and Dagenham E12000007  London     8791     8672       119   50.6   3441       2.5
7  E02000008 Barking and Dagenham 007 E09000002 Barking and Dagenham E12000007  London    11569    11564         5   81.5   4591       2.5
8  E02000009 Barking and Dagenham 008 E09000002 Barking and Dagenham E12000007  London     8395     8376        19   87.4   3212       2.6
9  E02000010 Barking and Dagenham 009 E09000002 Barking and Dagenham E12000007  London     8615     8615         0   76.8   3292       2.6
10 E02000011 Barking and Dagenham 010 E09000002 Barking and Dagenham E12000007  London     6187     6086       101   38.8   2289       2.7
                         geometry
1  MULTIPOLYGON (((532135.1 18...
2  MULTIPOLYGON (((548881.6 19...
3  MULTIPOLYGON (((549102.4 18...
4  MULTIPOLYGON (((551550 1873...
5  MULTIPOLYGON (((549099.6 18...
6  MULTIPOLYGON (((549819.9 18...
7  MULTIPOLYGON (((548171.4 18...
8  MULTIPOLYGON (((546855 1863...
9  MULTIPOLYGON (((549618.8 18...
10 MULTIPOLYGON (((550244.1 18...

Notice that when you’re reading a frame that isn’t “tidy”, it’s messy as hell - this is a geodataframe. Makes me miss the elegance of Geopandas somewhat. Still, it’s easy to import, and you get pretty plots. Given MOPAC data is in national grid, we’re also going to have to re-project this.

It’s also very easy to plot.

plot(lsoa_borders)
plotting the first 9 out of 12 attributes; use max.plot = 12 to plot all

No idea why it wants that much white space though….

Now, let’s reproject and do a spatial join to assign all of my crimes to an MSOA. Given we’re working on London, let’s change change everything to that. We’ll start by changing my crime dataframe, that currently has latitude and longitudes as just numbers, to a spatial dataframe with coordinates


'subset_spatial <- st_as_sf(subset_df, coords = c("Longitude", "Latitude"), 
                      crs = 4326, remove = FALSE)

subset_spatial'
[1] "subset_spatial <- st_as_sf(subset_df, coords = c(\"Longitude\", \"Latitude\"), \n                      crs = 4326, remove = FALSE)\n\nsubset_spatial"

Ah, we have missing values. Time to learn how to drop those.

In Pandas, we have easy functions to “drop_na” and “is_na” - I’m hoping to quickly find equivalents. My favourite Python approach to this is df.isna().sum(), counting how many “true” values you have when filtering like that. Can we duplicate that process?

sum(is.na(subset_df["Longitude"]))
[1] 82
sum(is.na(subset_df["Latitude"]))
[1] 82

We can! Glorious. We have 82 missing coordinates. Let’s drop all those rows.

clean_df <- subset_df[!rowSums(is.na(subset_df["Longitude"])), ]
clean_df

Annoyingly, while R does have a drop_na function, it doesn’t take a “subset” argument like Python, which means this slightly painful fudge.

We should now be able to form our spatial df.


subset_spatial <- st_as_sf(clean_df, coords = c("Longitude", "Latitude"), 
                      crs = 4326, remove = FALSE)

subset_spatial
Simple feature collection with 10419 features and 12 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: -0.492381 ymin: 51.28683 xmax: 0.273434 ymax: 51.68564
geographic CRS: WGS 84
First 10 features:
                                                           Crime.ID   Month                 Reported.by                Falls.within Longitude Latitude
1  628e0d673aa1b6a70479342a64b02884499df85b18dcd63cc9bff3cff9f704bc 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140035 51.58911
2  f8e9db16dca534a83493198a838567aa5adc9dd56496edc2fff5bb4c62b8303e 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140035 51.58911
3  cc34822074b130f141f16d02fdb2d500c86e22ae18324b43a3231b381af3f45c 2018-01 Metropolitan Police Service Metropolitan Police Service  0.135554 51.58499
4  10de581c3cd0a8c9b970824cd7589d13148d63a70b3115d95ef6c24dc0bd2c3b 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140035 51.58911
5  50ad5d2dfea24afec9e17218db62b3d29786775db1060634ae7d4a6e7cafc3ff 2018-01 Metropolitan Police Service Metropolitan Police Service  0.127794 51.58419
6  95abc6eb0b755c9250d19bbe0062fcd4a509b701964d89667401c9dc96ca257d 2018-01 Metropolitan Police Service Metropolitan Police Service  0.138439 51.57850
7  035cc894d732addb5009148d8e163e6360094cfe451f621348f1c0419b9cbc77 2018-01 Metropolitan Police Service Metropolitan Police Service  0.138439 51.57850
8  495cac920dcf9e0e4927074e8ac307f17d340f01c69e434c4a3721df017cd342 2018-01 Metropolitan Police Service Metropolitan Police Service  0.139479 51.57974
9  48234e70cbc22265ee7968da92df1ca72f83b45414cce486ec2203daa4e59fa2 2018-01 Metropolitan Police Service Metropolitan Police Service  0.135119 51.57849
10 3f4ba20780987c37816ff34fd0f7760cf503b2e648777f84ee0104432cb01d66 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140452 51.58110
                       Location LSOA.code                 LSOA.name Crime.type                         Last.outcome.category Context                  geometry
1    On or near Beansland Grove E01000027 Barking and Dagenham 001A   Burglary                       Offender sent to prison      NA POINT (0.140035 51.58911)
2    On or near Beansland Grove E01000027 Barking and Dagenham 001A   Burglary Investigation complete; no suspect identified      NA POINT (0.140035 51.58911)
3          On or near Rose Lane E01000027 Barking and Dagenham 001A   Burglary                     Status update unavailable      NA POINT (0.135554 51.58499)
4    On or near Beansland Grove E01000027 Barking and Dagenham 001A   Burglary                     Status update unavailable      NA POINT (0.140035 51.58911)
5         On or near Hope Close E01000028 Barking and Dagenham 001B   Burglary                     Status update unavailable      NA POINT (0.127794 51.58419)
6     On or near Geneva Gardens E01000029 Barking and Dagenham 001C   Burglary Investigation complete; no suspect identified      NA  POINT (0.138439 51.5785)
7     On or near Geneva Gardens E01000029 Barking and Dagenham 001C   Burglary                     Status update unavailable      NA  POINT (0.138439 51.5785)
8   On or near Yew Tree Gardens E01000029 Barking and Dagenham 001C   Burglary                     Status update unavailable      NA POINT (0.139479 51.57974)
9     On or near Portland Close E01000029 Barking and Dagenham 001C   Burglary                     Status update unavailable      NA POINT (0.135119 51.57849)
10 On or near Pedestrian Subway E01000030 Barking and Dagenham 001D    Robbery Investigation complete; no suspect identified      NA  POINT (0.140452 51.5811)
plot(subset_spatial)
plotting the first 9 out of 12 attributes; use max.plot = 12 to plot all

Success! That looks faintly promising. Now, let’s figure out how to re-project.


latlong = "+init=epsg:4326"
ukgrid = "+init=epsg:27700"
subset_osgb <- st_transform(subset_spatial, ukgrid)
GDAL Message 1: +init=epsg:XXXX syntax is deprecated. It might return a CRS with a non-EPSG compliant axis order.
subset_osgb
Simple feature collection with 10419 features and 12 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: 504499 ymin: 155908 xmax: 557677 ymax: 200168
projected CRS:  OSGB 1936 / British National Grid
First 10 features:
                                                           Crime.ID   Month                 Reported.by                Falls.within Longitude Latitude
1  628e0d673aa1b6a70479342a64b02884499df85b18dcd63cc9bff3cff9f704bc 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140035 51.58911
2  f8e9db16dca534a83493198a838567aa5adc9dd56496edc2fff5bb4c62b8303e 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140035 51.58911
3  cc34822074b130f141f16d02fdb2d500c86e22ae18324b43a3231b381af3f45c 2018-01 Metropolitan Police Service Metropolitan Police Service  0.135554 51.58499
4  10de581c3cd0a8c9b970824cd7589d13148d63a70b3115d95ef6c24dc0bd2c3b 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140035 51.58911
5  50ad5d2dfea24afec9e17218db62b3d29786775db1060634ae7d4a6e7cafc3ff 2018-01 Metropolitan Police Service Metropolitan Police Service  0.127794 51.58419
6  95abc6eb0b755c9250d19bbe0062fcd4a509b701964d89667401c9dc96ca257d 2018-01 Metropolitan Police Service Metropolitan Police Service  0.138439 51.57850
7  035cc894d732addb5009148d8e163e6360094cfe451f621348f1c0419b9cbc77 2018-01 Metropolitan Police Service Metropolitan Police Service  0.138439 51.57850
8  495cac920dcf9e0e4927074e8ac307f17d340f01c69e434c4a3721df017cd342 2018-01 Metropolitan Police Service Metropolitan Police Service  0.139479 51.57974
9  48234e70cbc22265ee7968da92df1ca72f83b45414cce486ec2203daa4e59fa2 2018-01 Metropolitan Police Service Metropolitan Police Service  0.135119 51.57849
10 3f4ba20780987c37816ff34fd0f7760cf503b2e648777f84ee0104432cb01d66 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140452 51.58110
                       Location LSOA.code                 LSOA.name Crime.type                         Last.outcome.category Context                geometry
1    On or near Beansland Grove E01000027 Barking and Dagenham 001A   Burglary                       Offender sent to prison      NA   POINT (548349 189976)
2    On or near Beansland Grove E01000027 Barking and Dagenham 001A   Burglary Investigation complete; no suspect identified      NA   POINT (548349 189976)
3          On or near Rose Lane E01000027 Barking and Dagenham 001A   Burglary                     Status update unavailable      NA POINT (548052 189507.9)
4    On or near Beansland Grove E01000027 Barking and Dagenham 001A   Burglary                     Status update unavailable      NA   POINT (548349 189976)
5         On or near Hope Close E01000028 Barking and Dagenham 001B   Burglary                     Status update unavailable      NA   POINT (547517 189404)
6     On or near Geneva Gardens E01000029 Barking and Dagenham 001C   Burglary Investigation complete; no suspect identified      NA   POINT (548273 188793)
7     On or near Geneva Gardens E01000029 Barking and Dagenham 001C   Burglary                     Status update unavailable      NA   POINT (548273 188793)
8   On or near Yew Tree Gardens E01000029 Barking and Dagenham 001C   Burglary                     Status update unavailable      NA   POINT (548341 188933)
9     On or near Portland Close E01000029 Barking and Dagenham 001C   Burglary                     Status update unavailable      NA   POINT (548043 188785)
10 On or near Pedestrian Subway E01000030 Barking and Dagenham 001D    Robbery Investigation complete; no suspect identified      NA   POINT (548404 189086)

Error messages in R are definitely harder to digest for me so far…I’m hoping that will pass with time. I’m also finding the documentation slightly harder to figure out, with fewer worked examples. Still, so far, so easily translateable! Now, let’s spatial join this up.

crime_with_msoa <- st_join(subset_osgb, lsoa_borders["MSOA11CD"])
crime_with_msoa
Simple feature collection with 10419 features and 13 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: 504499 ymin: 155908 xmax: 557677 ymax: 200168
projected CRS:  OSGB 1936 / British National Grid
First 10 features:
                                                           Crime.ID   Month                 Reported.by                Falls.within Longitude Latitude
1  628e0d673aa1b6a70479342a64b02884499df85b18dcd63cc9bff3cff9f704bc 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140035 51.58911
2  f8e9db16dca534a83493198a838567aa5adc9dd56496edc2fff5bb4c62b8303e 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140035 51.58911
3  cc34822074b130f141f16d02fdb2d500c86e22ae18324b43a3231b381af3f45c 2018-01 Metropolitan Police Service Metropolitan Police Service  0.135554 51.58499
4  10de581c3cd0a8c9b970824cd7589d13148d63a70b3115d95ef6c24dc0bd2c3b 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140035 51.58911
5  50ad5d2dfea24afec9e17218db62b3d29786775db1060634ae7d4a6e7cafc3ff 2018-01 Metropolitan Police Service Metropolitan Police Service  0.127794 51.58419
6  95abc6eb0b755c9250d19bbe0062fcd4a509b701964d89667401c9dc96ca257d 2018-01 Metropolitan Police Service Metropolitan Police Service  0.138439 51.57850
7  035cc894d732addb5009148d8e163e6360094cfe451f621348f1c0419b9cbc77 2018-01 Metropolitan Police Service Metropolitan Police Service  0.138439 51.57850
8  495cac920dcf9e0e4927074e8ac307f17d340f01c69e434c4a3721df017cd342 2018-01 Metropolitan Police Service Metropolitan Police Service  0.139479 51.57974
9  48234e70cbc22265ee7968da92df1ca72f83b45414cce486ec2203daa4e59fa2 2018-01 Metropolitan Police Service Metropolitan Police Service  0.135119 51.57849
10 3f4ba20780987c37816ff34fd0f7760cf503b2e648777f84ee0104432cb01d66 2018-01 Metropolitan Police Service Metropolitan Police Service  0.140452 51.58110
                       Location LSOA.code                 LSOA.name Crime.type                         Last.outcome.category Context  MSOA11CD
1    On or near Beansland Grove E01000027 Barking and Dagenham 001A   Burglary                       Offender sent to prison      NA E02000002
2    On or near Beansland Grove E01000027 Barking and Dagenham 001A   Burglary Investigation complete; no suspect identified      NA E02000002
3          On or near Rose Lane E01000027 Barking and Dagenham 001A   Burglary                     Status update unavailable      NA E02000002
4    On or near Beansland Grove E01000027 Barking and Dagenham 001A   Burglary                     Status update unavailable      NA E02000002
5         On or near Hope Close E01000028 Barking and Dagenham 001B   Burglary                     Status update unavailable      NA E02000002
6     On or near Geneva Gardens E01000029 Barking and Dagenham 001C   Burglary Investigation complete; no suspect identified      NA E02000002
7     On or near Geneva Gardens E01000029 Barking and Dagenham 001C   Burglary                     Status update unavailable      NA E02000002
8   On or near Yew Tree Gardens E01000029 Barking and Dagenham 001C   Burglary                     Status update unavailable      NA E02000002
9     On or near Portland Close E01000029 Barking and Dagenham 001C   Burglary                     Status update unavailable      NA E02000002
10 On or near Pedestrian Subway E01000030 Barking and Dagenham 001D    Robbery Investigation complete; no suspect identified      NA E02000002
                  geometry
1    POINT (548349 189976)
2    POINT (548349 189976)
3  POINT (548052 189507.9)
4    POINT (548349 189976)
5    POINT (547517 189404)
6    POINT (548273 188793)
7    POINT (548273 188793)
8    POINT (548341 188933)
9    POINT (548043 188785)
10   POINT (548404 189086)

That looks like it worked - it defaults to a left join. Now, let’s group by offence type and MSOA, so as to get a count of robbery and burglary per MSOA for this month. We should then have all the code we need to create our function.

msoa_list<- crime_with_msoa %>%
  group_by(MSOA11CD, Crime.type) %>%
  summarize(count_by_msoa = n())
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
msoa_list
Simple feature collection with 1722 features and 3 fields
geometry type:  GEOMETRY
dimension:      XY
bbox:           xmin: 504499 ymin: 155908 xmax: 557677 ymax: 200168
projected CRS:  OSGB 1936 / British National Grid

It works! We’ll need to fill every missing value with 0, drop the geometry column, and then repeat the process for every month, and then we’re in business.

We’ll have to check for any values that aren’t MSOAs that aren’t present, and if they’re not, add a 0. I’m going to do this for robbery and burglary independently.

class(msoa_list)
[1] "sf"         "grouped_df" "tbl_df"     "tbl"        "data.frame"

We need to remove the geometry column. Currently, our object is a spatial dataframe (sf) and a tibble (a tidyverse specific dataframe type) which is stopping me from removing the geometry data.

msoa_pivot_tibble <- as_tibble(msoa_list)
msoa_pivot_tibble
class(msoa_pivot_tibble)
[1] "tbl_df"     "tbl"        "data.frame"

We’ve now removed the spatial frame function, and should be able to drop the last column (geometry.)

msoa_pivot_tibble <- msoa_pivot_tibble[0:3]
msoa_pivot_tibble

We now need to fill our missing values. Rather than iterate or similar, I’ll just add an entire df filled with 0s for both crime types, then drop any duplicates - at least, that’s how I’d do it in Python, and shall try to do here!

#creating a df with all msoa names, for robbery and burglary
msoa_zero_df_robbery <- unique(as_tibble(lsoa_borders)["MSOA11CD"])
msoa_zero_df_burglary <- unique(as_tibble(lsoa_borders)["MSOA11CD"])

#adding our crime type column 
msoa_zero_df_burglary["Crime.type"] = "Burglary"
msoa_zero_df_robbery["Crime.type"] = "Robbery"

#Creating a "count" column identical to our pivot, and filling it with 0
msoa_zero_df_burglary["count_by_msoa"] = as.numeric(0)
msoa_zero_df_robbery["count_by_msoa"] = as.numeric(0)

msoa_zero_df_robbery

I’m a little worried about the “dbl” class, but let’s ignore that for now. Now, we need to concatenate both, and add them to our MSOA pivot.

duplicate_concat <- rbind(msoa_zero_df_robbery, msoa_zero_df_burglary)
duplicate_concat

It seems to have worked. That said, the fact there is no clear function for concatenation (in contrast to pd.concatenate in Pandas) surprises me. Now, let’s finally concatenate everything, and remove duplicates. That should form our final monthly df, and we can then combine all our previous steps into a function.


df_with_dups <- rbind(msoa_pivot_tibble, duplicate_concat)

df_with_dups

It’s noticeable how much harder finding documentation is for R than Pandas - while the drop_duplicates function is front and center for any searches in Python, a similar search in R reveals plenty of hacky filters, but the “distinct” function seems to be what I’m actually looking for.

#creating a filter for duplicates columns, which should ignore the first instance
dup_filters <- duplicated(df_with_dups[0:2])


monthly_df <- filter(df_with_dups, !dup_filters)
monthly_df

That should now be all our values. As a sanity check, let’s make sure we have the right number of rows, using R’s “dim” function (equivalent to shape in Pandas) to check how many unique values we would expect.

dim(unique(as_tibble(lsoa_borders)["MSOA11CD"]))[1] * 2
[1] 1966

We’ve got 2 extra…a bit weird, but not end of world. Let’s leave it at that.

We now need to add our monthly date to this dataframe

#select the first unique value of months in the original dataframe
month <- unique(test_df["Month"])[1,1]
monthly_df["Month"] <- month
monthly_df

Now, let’s bring all our previous work together into a function (and fix my awkward prior msoa/lsoa typo)

#quick initial function to generate our MSOA borde spatial frame, to avoid it sitting in the initial frame and gobbling loads of memory.
generate_msoa_borders <- function(file){
  msoa_borders <- st_read(file, crs=27700)
  return(msoa_borders)
}

make_month_pivot <- function(file){
  #define our CRS
  latlong = "+init=epsg:4326"
  ukgrid = "+init=epsg:27700"
  #read our crime from the file
  test_df <- read.csv(file)
  #select only our target crime types
  subset_df <- filter(test_df, Crime.type=="Burglary" | Crime.type=="Robbery")
  #remove any rows with a long/lat coordinate
  clean_df <- subset_df[!rowSums(is.na(subset_df["Longitude"])), ]
  #generate a spatial df
  subset_spatial <- st_as_sf(clean_df, coords = c("Longitude", "Latitude"), 
                      crs = 4326, remove = FALSE)
  #reproject to uk grid coords
  subset_osgb <- st_transform(subset_spatial, ukgrid)
  #spatially join to assign to an MSOA
  crime_with_msoa <- st_join(subset_osgb, msoa_borders["MSOA11CD"])
  #summarise by count of MSOA
  msoa_list<- crime_with_msoa %>%
    group_by(MSOA11CD, Crime.type) %>%
    summarize(count_by_msoa = n())
  #return to a non-geographic msoa
  msoa_pivot_tibble <- as_tibble(msoa_list)
  msoa_pivot_tibble <- msoa_pivot_tibble[0:3]
  #creating a df with all msoa names, for robbery and burglary
  msoa_zero_df_robbery <- unique(as_tibble(msoa_borders)["MSOA11CD"])
  msoa_zero_df_burglary <- unique(as_tibble(msoa_borders)["MSOA11CD"])
  #adding our crime type column 
  msoa_zero_df_burglary["Crime.type"] = "Burglary"
  msoa_zero_df_robbery["Crime.type"] = "Robbery"
  #Creating a "count" column identical to our pivot, and filling it with 0
  msoa_zero_df_burglary["count_by_msoa"] = as.numeric(0)
  msoa_zero_df_robbery["count_by_msoa"] = as.numeric(0)
  duplicate_concat <- rbind(msoa_zero_df_robbery, msoa_zero_df_burglary)
  df_with_dups <- rbind(msoa_pivot_tibble, duplicate_concat)
  #creating a filter for duplicates columns, which should ignore the first instance
  dup_filters <- duplicated(df_with_dups[0:2])
  monthly_df <- filter(df_with_dups, !dup_filters)
  #re-add our month column
  month <- unique(test_df["Month"])[1,1]
  monthly_df["Month"] <- month
  return(monthly_df)
}

We’ve now got our tooling for a data pipeline ready to go! We can now run this on every single month of data, and aggregate into a historical combined data-set.

Data.Police.UK comes as a bunch of nested-subdirectories…in hindsight, I probably should have looked at their API, but for now let’s power ahead and figure out how to extract a list of all the CSV files in our folder and the various sub-directories.

list.files(path = "crimes")
 [1] "2018-01" "2018-02" "2018-03" "2018-04" "2018-05" "2018-06" "2018-07" "2018-08" "2018-09" "2018-10" "2018-11" "2018-12" "2019-01" "2019-02" "2019-03" "2019-04"
[17] "2019-05" "2019-06" "2019-07" "2019-08" "2019-09" "2019-10" "2019-11" "2019-12" "2020-01" "2020-02" "2020-03" "2020-04" "2020-05" "2020-06" "2020-07" "2020-08"
[33] "2020-09" "2020-10" "2020-11" "2020-12"

As we suspected, the nested directories cause an issue - guess we’re going to have to learn about loops in R! Let’s iterate over our list of subfolders, and re-apply the function to each.

subfolders <- list.files(path = "crimes")
file_list <- list()
for (folder in subfolders){
  folder_subdir <- "crimes/"
  #concatenate to get our total subfolder directory - hacky but will work here.
  sub_path <- paste(folder_subdir, folder, sep="")
  list.files(sub_path)
  file_list <- list(file_list, paste(sub_path,"/", list.files(sub_path), sep=""))
  }

The bad news is, this totally didn’t work. The good news is, it led me to the far cleaner, “recursive” version of the read files function.

list.files(path = "crimes", recursive=T)
 [1] "2018-01/2018-01-metropolitan-street.csv" "2018-02/2018-02-metropolitan-street.csv" "2018-03/2018-03-metropolitan-street.csv"
 [4] "2018-04/2018-04-metropolitan-street.csv" "2018-05/2018-05-metropolitan-street.csv" "2018-06/2018-06-metropolitan-street.csv"
 [7] "2018-07/2018-07-metropolitan-street.csv" "2018-08/2018-08-metropolitan-street.csv" "2018-09/2018-09-metropolitan-street.csv"
[10] "2018-10/2018-10-metropolitan-street.csv" "2018-11/2018-11-metropolitan-street.csv" "2018-12/2018-12-metropolitan-street.csv"
[13] "2019-01/2019-01-metropolitan-street.csv" "2019-02/2019-02-metropolitan-street.csv" "2019-03/2019-03-metropolitan-street.csv"
[16] "2019-04/2019-04-metropolitan-street.csv" "2019-05/2019-05-metropolitan-street.csv" "2019-06/2019-06-metropolitan-street.csv"
[19] "2019-07/2019-07-metropolitan-street.csv" "2019-08/2019-08-metropolitan-street.csv" "2019-09/2019-09-metropolitan-street.csv"
[22] "2019-10/2019-10-metropolitan-street.csv" "2019-11/2019-11-metropolitan-street.csv" "2019-12/2019-12-metropolitan-street.csv"
[25] "2020-01/2020-01-metropolitan-street.csv" "2020-02/2020-02-metropolitan-street.csv" "2020-03/2020-03-metropolitan-street.csv"
[28] "2020-04/2020-04-metropolitan-street.csv" "2020-05/2020-05-metropolitan-street.csv" "2020-06/2020-06-metropolitan-street.csv"
[31] "2020-07/2020-07-metropolitan-street.csv" "2020-08/2020-08-metropolitan-street.csv" "2020-09/2020-09-metropolitan-street.csv"
[34] "2020-10/2020-10-metropolitan-street.csv" "2020-11/2020-11-metropolitan-street.csv" "2020-12/2020-12-metropolitan-street.csv"

First, let’s create an empty dataframe we can concatenate all teh rest to.

empty_df <- tibble(
MSOA11CD = "", 
Crime.type= "",
count_by_msoa= "",
Month= ""
)
empty_df

Annoyingly, I don’t seem to be able to cr

msoa_borders <- generate_msoa_borders("msoa_borders/MSOA_2011_London_gen_MHW.tab")
Reading layer `MSOA_2011_London_gen_MHW' from data source `C:\Users\andre\Dropbox\Data Projects\Covid_crime_shift\msoa_borders\MSOA_2011_London_gen_MHW.tab' using driver `MapInfo File'
Simple feature collection with 983 features and 12 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 503574.2 ymin: 155850.8 xmax: 561956.7 ymax: 200933.6
projected CRS:  OSGB 1936 / British National Grid
msoa_borders
Simple feature collection with 983 features and 12 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 503574.2 ymin: 155850.8 xmax: 561956.7 ymax: 200933.6
projected CRS:  OSGB 1936 / British National Grid
First 10 features:
    MSOA11CD                 MSOA11NM   LAD11CD              LAD11NM   RGN11CD RGN11NM UsualRes HholdRes ComEstRes PopDen Hholds AvHholdSz
1  E02000001       City of London 001 E09000001       City of London E12000007  London     7375     7187       188   25.5   4385       1.6
2  E02000002 Barking and Dagenham 001 E09000002 Barking and Dagenham E12000007  London     6775     6724        51   31.3   2713       2.5
3  E02000003 Barking and Dagenham 002 E09000002 Barking and Dagenham E12000007  London    10045    10033        12   46.9   3834       2.6
4  E02000004 Barking and Dagenham 003 E09000002 Barking and Dagenham E12000007  London     6182     5937       245   24.8   2318       2.6
5  E02000005 Barking and Dagenham 004 E09000002 Barking and Dagenham E12000007  London     8562     8562         0   72.1   3183       2.7
6  E02000007 Barking and Dagenham 006 E09000002 Barking and Dagenham E12000007  London     8791     8672       119   50.6   3441       2.5
7  E02000008 Barking and Dagenham 007 E09000002 Barking and Dagenham E12000007  London    11569    11564         5   81.5   4591       2.5
8  E02000009 Barking and Dagenham 008 E09000002 Barking and Dagenham E12000007  London     8395     8376        19   87.4   3212       2.6
9  E02000010 Barking and Dagenham 009 E09000002 Barking and Dagenham E12000007  London     8615     8615         0   76.8   3292       2.6
10 E02000011 Barking and Dagenham 010 E09000002 Barking and Dagenham E12000007  London     6187     6086       101   38.8   2289       2.7
                         geometry
1  MULTIPOLYGON (((532135.1 18...
2  MULTIPOLYGON (((548881.6 19...
3  MULTIPOLYGON (((549102.4 18...
4  MULTIPOLYGON (((551550 1873...
5  MULTIPOLYGON (((549099.6 18...
6  MULTIPOLYGON (((549819.9 18...
7  MULTIPOLYGON (((548171.4 18...
8  MULTIPOLYGON (((546855 1863...
9  MULTIPOLYGON (((549618.8 18...
10 MULTIPOLYGON (((550244.1 18...

Our MSOA border helper functions seems to work. Now, time to do the heavy lifting!

subfiles <- list.files(path = "crimes", recursive=T)

for (file in subfiles){
  folder_subdir <- "crimes/"
  #concatenate to get our total subfolder directory - hacky but will work here.
  sub_path <- paste(folder_subdir, file, sep="")
  monthly_df <- make_month_pivot(sub_path)
  empty_df <- rbind(empty_df, monthly_df)
}
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'MSOA11CD'. You can override using the `.groups` argument.

That seems to have worked! The processing time was longer than I expected (which is probably something to do with how R stores memory) - let’s look at our previously empty dataframe.

empty_df
unique(empty_df["Month"])

So we now have a combined dataframe of just under 71,000 rows, for 37 individual months beetween January 2018 and December 2020, for every robbery and burglary in London, assigned to an MSOA. I’d call that a win!

This has been somewhat more painful than I expected, so before going any further, let’s figure out how to save this file. Given it’s all strings and integers, a simple CSV should do for now.

write.csv(empty_df,"msoa_crime_matrix.csv")
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vay4gV2hlbiB5b3UgZXhlY3V0ZSBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSByZXN1bHRzIGFwcGVhciBiZW5lYXRoIHRoZSBjb2RlLiANCg0KVHJ5IGV4ZWN1dGluZyB0aGlzIGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqUnVuKiBidXR0b24gd2l0aGluIHRoZSBjaHVuayBvciBieSBwbGFjaW5nIHlvdXIgY3Vyc29yIGluc2lkZSBpdCBhbmQgcHJlc3NpbmcgKkN0cmwrU2hpZnQrRW50ZXIqLiANCg0KDQoNCkFkZCBhIG5ldyBjaHVuayBieSBjbGlja2luZyB0aGUgKkluc2VydCBDaHVuayogYnV0dG9uIG9uIHRoZSB0b29sYmFyIG9yIGJ5IHByZXNzaW5nICpDdHJsK0FsdCtJKi4NCg0KV2hlbiB5b3Ugc2F2ZSB0aGUgbm90ZWJvb2ssIGFuIEhUTUwgZmlsZSBjb250YWluaW5nIHRoZSBjb2RlIGFuZCBvdXRwdXQgd2lsbCBiZSBzYXZlZCBhbG9uZ3NpZGUgaXQgKGNsaWNrIHRoZSAqUHJldmlldyogYnV0dG9uIG9yIHByZXNzICpDdHJsK1NoaWZ0K0sqIHRvIHByZXZpZXcgdGhlIEhUTUwgZmlsZSkuDQoNClRoZSBwcmV2aWV3IHNob3dzIHlvdSBhIHJlbmRlcmVkIEhUTUwgY29weSBvZiB0aGUgY29udGVudHMgb2YgdGhlIGVkaXRvci4gQ29uc2VxdWVudGx5LCB1bmxpa2UgKktuaXQqLCAqUHJldmlldyogZG9lcyBub3QgcnVuIGFueSBSIGNvZGUgY2h1bmtzLiBJbnN0ZWFkLCB0aGUgb3V0cHV0IG9mIHRoZSBjaHVuayB3aGVuIGl0IHdhcyBsYXN0IHJ1biBpbiB0aGUgZWRpdG9yIGlzIGRpc3BsYXllZC4NCiMjIFVzZWZ1bCBSZXNvdXJjZXMNCi0gTWF0dCBBc2hieSBDcmltZSBNYXBwaW5nIGNvdXJzZTogaHR0cHM6Ly9naXRodWIuY29tL21wamFzaGJ5L2NyaW1lbWFwcGluZy8NCi0gU3BhdGlhbCBNb2RlbGxpbmcgZm9yIERhdGEgU2NpZW50aXN0czogaHR0cHM6Ly9nZHNsLXVsLmdpdGh1Yi5pby9zYW4vDQotIFIgZm9yIERhdGEgU2NpZW5jZTogaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9pbmRleC5odG1sDQotIEdlb2NvbXB1dGF0aW9uIHdpdGggUjogaHR0cHM6Ly9nZW9jb21wci5yb2JpbmxvdmVsYWNlLm5ldC8NCg0KDQoNCiMjIEV4cGxvcmluZyBQcmVkaWN0b3JzIG9mIENPVklEIENyaW1lIFNoaWZ0cw0KSW4gdGhpcyBub3RlYm9vaywgSSdsbCBiZSBwcmVkaXRpbmcgY3JpbWUgdHJlbmRzIGluIExvbmRvbiBieSBNU09BLCBhbmQgbG9va2luZyBhdCB3aGVyZSBDT1ZJRCBoYXMgYmVlbiBtb3N0IGltcGFjdGl2ZS4gIEknbGwgdGhlbiB0cnkgdG8gZmluZCBjb3JyZWxhdGVzLg0KDQojIyMjIFRhc2tzDQoxLiBSZWFkIGFsbCBidXJnbGFyeSBhbmQgcm9iYmVyeSBkYXRhIC0gMjAxOCBvbndhcmRzDQoyLiBBc3NpZ24gdG8gTVNPQQ0KMy4gUHJlZGljdCB0cmVuZCBieSBNU09BDQo0LiBJZGVudGlmeSBlcnJvciBieSBNU09BDQo1LiBWaXN1YWxpc2UgYW5kIG1vZGVsDQoNCmBgYHtyfQ0KIyBEYXRhIG1hbmlwdWxhdGlvbiwgdHJhbnNmb3JtYXRpb24gYW5kIHZpc3VhbGlzYXRpb24NCmxpYnJhcnkodGlkeXZlcnNlKQ0KIyBOaWNlIHRhYmxlcw0KbGlicmFyeShrYWJsZUV4dHJhKQ0KIyBTaW1wbGUgZmVhdHVyZXMgKGEgc3RhbmRhcmRpc2VkIHdheSB0byBlbmNvZGUgdmVjdG9yIGRhdGEgaWUuIHBvaW50cywgbGluZXMsIHBvbHlnb25zKQ0KbGlicmFyeShzZikgDQojIFNwYXRpYWwgb2JqZWN0cyBjb252ZXJzaW9uDQpsaWJyYXJ5KHNwKSANCiMgVGhlbWF0aWMgbWFwcw0KbGlicmFyeSh0bWFwKSANCiMgQ29sb3VyIHBhbGV0dGVzDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikgDQojIE1vcmUgY29sb3VyIHBhbGV0dGVzDQpsaWJyYXJ5KHZpcmlkaXMpDQoNCmxpYnJhcnkocmFzdGVyKSAgIyByYXN0ZXIgZGF0YQ0KbGlicmFyeShyZ2RhbCkgICMgaW5wdXQvb3V0cHV0LCBwcm9qZWN0aW9ucw0KbGlicmFyeShyZ2VvcykgICMgZ2VvbWV0cnkgb3BzDQpsaWJyYXJ5KHNwZGVwKSAgIyBzcGF0aWFsIGRlcGVuZGVuY2UNCg0KbGlicmFyeShNZXRyaWNzKQ0KbGlicmFyeShjYXJldCkNCg0KYGBgDQpPbmUgb2YgdGhlIGZpcnN0IHRoaW5ncyBJIG5vdGljZSBpcyB0aGF0IHdoaWxlIFB5dGhvbiBjb2RlIGlzIGdlbmVyYWxseSBxdWl0ZSBjYXJlZnVsIGFib3V0IGltcG9ydHMsIGhlcmUgd2UgZ2xvYmFsbHkgaW1wb3J0IGV2ZXJ5dGhpbmcuLi53aGljaCBpcyBuaWNlLCBidXQgSSdtIGFsc28gbm90IHF1aXRlIGNsZWFyIHdoaWNoIGZ1bmN0aW9ucyBhcmUgY29taW5nIGZyb20gd2hpY2ggbGlicmFyaWVzLg0KDQpOb3csIGxldCdzIGltcG9ydCBhbGwgb3VyIGNyaW1lIGRhdGEuIExldCdzIHN0YXJ0IGJ5IG9uZSBkYXRhZnJhbWUgYmVmb3JlIGZpZ3VyaW5nIGhvdyB0byBhdXRvbWF0ZSBhbmQgY29uY2F0ZW5hdGUuICBOb3RpY2UgUiB1c2VzIHNsYXNoZXMgdGhhdCBhcmUgdGhlIG90ZWhyIHdheSB0byBQeXRob24gYW5kIFdpbmRvd3MNCg0KYGBge3J9DQp0ZXN0X2RmIDwtIHJlYWQuY3N2KCJjcmltZXMvMjAxOC0wMS8yMDE4LTAxLW1ldHJvcG9saXRhbi1zdHJlZXQuY3N2IikNCnRlc3RfZGYNCg0KYGBgDQpMZXQncyBsb29rIGF0IGFsbCB0aGUgdW5pcXVlIGNyaW1lIHR5cGVzLiAgTm90aWNlIGhvdyB3ZSBoYXZlIGEgc2ltaWxhciB0byB0aGUgInVuaXF1ZSIgbWV0aG9kIGluIFB5dGhvbiwgYW5kIGFjY2VzcyBhIHNwZWNpZmljIGZ1bmN0aW9uIHVzaW5nIHRoZSBzYW1lIHN5bnRheC4gIA0KDQpgYGB7cn0NCnVuaXF1ZSh0ZXN0X2RmWyJDcmltZS50eXBlIl0pDQpgYGANClRvIGF2b2lkIHRoaXMgZ2V0dGluZyBwYXJ0aWN1bGFybHkgY29tcHV0YXRpb25hbGx5IGludGVuc2l2ZSwgbGV0J3Mgd3JpdGUgYSBmdW5jdGlvbiB0byBwdWxsIG91dCByb2JiZXJpZXMgYW5kIGJ1cmdsYXJpZXMsIGFuZCBhc3NpZ24gdGhlbSBhIHNwZWNpZmljIE1TT0EuIFRoZW4gd2UgY2FuIGl0ZXJhdGUgb3ZlciBhbGwgb3VyIG1vbnRocyBhbmQgZ2V0IG1vbnRobHkgY291bnRzIGZvciBlYWNoIG9mZmVuY2UgdHlwZS4NCg0KYGBge3J9DQpzdWJzZXRfZGYgPC0gZmlsdGVyKHRlc3RfZGYsIENyaW1lLnR5cGU9PSJCdXJnbGFyeSIgfCBDcmltZS50eXBlPT0iUm9iYmVyeSIpDQpzdWJzZXRfZGYNCmBgYA0KV2VpcmRseSwgaGVyZSBJICpkb24ndCogbmVlZCBxdW90YXRpb24gbWFya3MgZm9yIG15IGNvbHVtIG5hbWUuLi5ub3Qgc3VyZSB3aGF0J3MgZHJpdmluZyB0aGF0LiBCdXQgaXQncyBlYXN5IHRvIHNlbGVjdCBhIHN1YnNldCBieSBjb2x1bW4sIGFuZCB1c2UgbG9naWNhbCBjb21wYXJhdG9ycy4NCg0KV2l0aCB0aGF0IGluIG1pbmQsIGxldCdzIG5vdyByZWFkIG91ciBNU09BIGJvcmRlcnMgYW5kIGFzc2lnbiB0aGVzZSB0byBhbiBNU09BLiBJJ3ZlIHNldCB0aGUgT1NHQiBDUlMgY29kZSBhcyBpdCBkb2Vzbid0IHNlZW0gdG8gYXV0b21hdGljYWxseSBhc3NpZ24gaXQuDQoNCmBgYHtyfQ0KbHNvYV9ib3JkZXJzIDwtIHN0X3JlYWQoIm1zb2FfYm9yZGVycy9NU09BXzIwMTFfTG9uZG9uX2dlbl9NSFcudGFiIiwgY3JzPTI3NzAwKQ0KbHNvYV9ib3JkZXJzDQoNCmBgYA0KTm90aWNlIHRoYXQgd2hlbiB5b3UncmUgcmVhZGluZyBhIGZyYW1lIHRoYXQgaXNuJ3QgInRpZHkiLCBpdCdzIG1lc3N5IGFzIGhlbGwgLSB0aGlzIGlzIGEgZ2VvZGF0YWZyYW1lLiAgTWFrZXMgbWUgbWlzcyB0aGUgZWxlZ2FuY2Ugb2YgR2VvcGFuZGFzIHNvbWV3aGF0LiBTdGlsbCwgaXQncyBlYXN5IHRvIGltcG9ydCwgYW5kIHlvdSBnZXQgcHJldHR5IHBsb3RzLiBHaXZlbiBNT1BBQyBkYXRhIGlzIGluIG5hdGlvbmFsIGdyaWQsIHdlJ3JlIGFsc28gZ29pbmcgdG8gaGF2ZSB0byByZS1wcm9qZWN0IHRoaXMuIA0KDQpJdCdzIGFsc28gdmVyeSBlYXN5IHRvIHBsb3QuDQoNCmBgYHtyfQ0KcGxvdChsc29hX2JvcmRlcnMpDQpgYGANCiBObyBpZGVhIHdoeSBpdCB3YW50cyB0aGF0IG11Y2ggd2hpdGUgc3BhY2UgdGhvdWdoLi4uLg0KIA0KIE5vdywgbGV0J3MgcmVwcm9qZWN0IGFuZCBkbyBhIHNwYXRpYWwgam9pbiB0byBhc3NpZ24gYWxsIG9mIG15IGNyaW1lcyB0byBhbiBNU09BLiBHaXZlbiB3ZSdyZSB3b3JraW5nIG9uIExvbmRvbiwgbGV0J3MgY2hhbmdlIGNoYW5nZSBldmVyeXRoaW5nIHRvIHRoYXQuIFdlJ2xsIHN0YXJ0IGJ5IGNoYW5naW5nIG15IGNyaW1lIGRhdGFmcmFtZSwgdGhhdCBjdXJyZW50bHkgaGFzIGxhdGl0dWRlIGFuZCBsb25naXR1ZGVzIGFzIGp1c3QgbnVtYmVycywgdG8gYSBzcGF0aWFsIGRhdGFmcmFtZSB3aXRoIGNvb3JkaW5hdGVzDQpgYGB7cn0NCg0KJ3N1YnNldF9zcGF0aWFsIDwtIHN0X2FzX3NmKHN1YnNldF9kZiwgY29vcmRzID0gYygiTG9uZ2l0dWRlIiwgIkxhdGl0dWRlIiksIA0KICAgICAgICAgICAgICAgICAgICAgIGNycyA9IDQzMjYsIHJlbW92ZSA9IEZBTFNFKQ0KDQpzdWJzZXRfc3BhdGlhbCcNCg0KYGBgDQoNCkFoLCB3ZSBoYXZlIG1pc3NpbmcgdmFsdWVzLiAgVGltZSB0byBsZWFybiBob3cgdG8gZHJvcCB0aG9zZS4NCg0KSW4gUGFuZGFzLCB3ZSBoYXZlIGVhc3kgZnVuY3Rpb25zIHRvICJkcm9wX25hIiBhbmQgImlzX25hIiAtIEknbSBob3BpbmcgdG8gcXVpY2tseSBmaW5kIGVxdWl2YWxlbnRzLiAgTXkgZmF2b3VyaXRlIFB5dGhvbiBhcHByb2FjaCB0byB0aGlzIGlzIGRmLmlzbmEoKS5zdW0oKSwgY291bnRpbmcgaG93IG1hbnkgInRydWUiIHZhbHVlcyB5b3UgaGF2ZSB3aGVuIGZpbHRlcmluZyBsaWtlIHRoYXQuICBDYW4gd2UgZHVwbGljYXRlIHRoYXQgcHJvY2Vzcz8NCg0KYGBge3J9DQpzdW0oaXMubmEoc3Vic2V0X2RmWyJMb25naXR1ZGUiXSkpDQpgYGANCmBgYHtyfQ0Kc3VtKGlzLm5hKHN1YnNldF9kZlsiTGF0aXR1ZGUiXSkpDQpgYGANCg0KV2UgY2FuISAgR2xvcmlvdXMuICBXZSBoYXZlIDgyIG1pc3NpbmcgY29vcmRpbmF0ZXMuICBMZXQncyBkcm9wIGFsbCB0aG9zZSByb3dzLg0KDQpgYGB7cn0NCmNsZWFuX2RmIDwtIHN1YnNldF9kZlshcm93U3Vtcyhpcy5uYShzdWJzZXRfZGZbIkxvbmdpdHVkZSJdKSksIF0NCmNsZWFuX2RmDQpgYGANCkFubm95aW5nbHksIHdoaWxlIFIgZG9lcyBoYXZlIGEgZHJvcF9uYSBmdW5jdGlvbiwgaXQgZG9lc24ndCB0YWtlIGEgInN1YnNldCIgYXJndW1lbnQgbGlrZSBQeXRob24sIHdoaWNoIG1lYW5zIHRoaXMgc2xpZ2h0bHkgcGFpbmZ1bCBmdWRnZS4NCg0KV2Ugc2hvdWxkIG5vdyBiZSBhYmxlIHRvIGZvcm0gb3VyIHNwYXRpYWwgZGYuDQoNCmBgYHtyfQ0KDQpzdWJzZXRfc3BhdGlhbCA8LSBzdF9hc19zZihjbGVhbl9kZiwgY29vcmRzID0gYygiTG9uZ2l0dWRlIiwgIkxhdGl0dWRlIiksIA0KICAgICAgICAgICAgICAgICAgICAgIGNycyA9IDQzMjYsIHJlbW92ZSA9IEZBTFNFKQ0KDQpzdWJzZXRfc3BhdGlhbA0KYGBgDQoNCg0KYGBge3J9DQpwbG90KHN1YnNldF9zcGF0aWFsKQ0KYGBgDQpTdWNjZXNzISBUaGF0IGxvb2tzIGZhaW50bHkgcHJvbWlzaW5nLiAgTm93LCBsZXQncyBmaWd1cmUgb3V0IGhvdyB0byByZS1wcm9qZWN0Lg0KDQpgYGB7cn0NCg0KbGF0bG9uZyA9ICIraW5pdD1lcHNnOjQzMjYiDQp1a2dyaWQgPSAiK2luaXQ9ZXBzZzoyNzcwMCINCg0KYGBgDQoNCmBgYHtyfQ0Kc3Vic2V0X29zZ2IgPC0gc3RfdHJhbnNmb3JtKHN1YnNldF9zcGF0aWFsLCB1a2dyaWQpDQpzdWJzZXRfb3NnYg0KYGBgDQoNCkVycm9yIG1lc3NhZ2VzIGluIFIgYXJlICBkZWZpbml0ZWx5IGhhcmRlciB0byBkaWdlc3QgZm9yIG1lIHNvIGZhci4uLkknbSBob3BpbmcgdGhhdCB3aWxsIHBhc3Mgd2l0aCB0aW1lLiBJJ20gYWxzbyBmaW5kaW5nIHRoZSBkb2N1bWVudGF0aW9uIHNsaWdodGx5IGhhcmRlciB0byBmaWd1cmUgb3V0LCB3aXRoIGZld2VyIHdvcmtlZCBleGFtcGxlcy4gU3RpbGwsIHNvIGZhciwgc28gZWFzaWx5IHRyYW5zbGF0ZWFibGUhIE5vdywgbGV0J3Mgc3BhdGlhbCBqb2luIHRoaXMgdXAuDQoNCg0KDQpgYGB7cn0NCmNyaW1lX3dpdGhfbXNvYSA8LSBzdF9qb2luKHN1YnNldF9vc2diLCBsc29hX2JvcmRlcnNbIk1TT0ExMUNEIl0pDQpjcmltZV93aXRoX21zb2ENCmBgYA0KVGhhdCBsb29rcyBsaWtlIGl0IHdvcmtlZCAtIGl0IGRlZmF1bHRzIHRvIGEgbGVmdCBqb2luLiAgTm93LCBsZXQncyBncm91cCBieSBvZmZlbmNlIHR5cGUgYW5kIE1TT0EsIHNvIGFzIHRvIGdldCBhIGNvdW50IG9mIHJvYmJlcnkgYW5kIGJ1cmdsYXJ5IHBlciBNU09BIGZvciB0aGlzIG1vbnRoLiAgV2Ugc2hvdWxkIHRoZW4gaGF2ZSBhbGwgdGhlIGNvZGUgd2UgbmVlZCB0byBjcmVhdGUgb3VyIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCm1zb2FfbGlzdDwtIGNyaW1lX3dpdGhfbXNvYSAlPiUNCiAgZ3JvdXBfYnkoTVNPQTExQ0QsIENyaW1lLnR5cGUpICU+JQ0KICBzdW1tYXJpemUoY291bnRfYnlfbXNvYSA9IG4oKSkNCg0KbXNvYV9saXN0DQpgYGANCkl0IHdvcmtzISAgV2UnbGwgbmVlZCB0byBmaWxsIGV2ZXJ5IG1pc3NpbmcgdmFsdWUgd2l0aCAwLCBkcm9wIHRoZSBnZW9tZXRyeSBjb2x1bW4sIGFuZCB0aGVuIHJlcGVhdCB0aGUgcHJvY2VzcyBmb3IgZXZlcnkgbW9udGgsIGFuZCB0aGVuIHdlJ3JlIGluIGJ1c2luZXNzLg0KDQpXZSdsbCBoYXZlIHRvIGNoZWNrIGZvciBhbnkgdmFsdWVzIHRoYXQgYXJlbid0IE1TT0FzIHRoYXQgYXJlbid0IHByZXNlbnQsIGFuZCBpZiB0aGV5J3JlIG5vdCwgYWRkIGEgMC4gSSdtIGdvaW5nIHRvIGRvIHRoaXMgZm9yIHJvYmJlcnkgYW5kIGJ1cmdsYXJ5IGluZGVwZW5kZW50bHkuDQoNCmBgYHtyfQ0KY2xhc3MobXNvYV9saXN0KQ0KDQpgYGANCldlIG5lZWQgdG8gcmVtb3ZlIHRoZSBnZW9tZXRyeSBjb2x1bW4uICBDdXJyZW50bHksIG91ciBvYmplY3QgaXMgYSBzcGF0aWFsIGRhdGFmcmFtZSAoc2YpIGFuZCBhIHRpYmJsZSAoYSB0aWR5dmVyc2Ugc3BlY2lmaWMgZGF0YWZyYW1lIHR5cGUpIHdoaWNoIGlzIHN0b3BwaW5nIG1lIGZyb20gcmVtb3ZpbmcgdGhlIGdlb21ldHJ5IGRhdGEuDQoNCmBgYHtyfQ0KbXNvYV9waXZvdF90aWJibGUgPC0gYXNfdGliYmxlKG1zb2FfbGlzdCkNCm1zb2FfcGl2b3RfdGliYmxlDQpgYGANCmBgYHtyfQ0KY2xhc3MobXNvYV9waXZvdF90aWJibGUpDQpgYGANCldlJ3ZlIG5vdyByZW1vdmVkIHRoZSBzcGF0aWFsIGZyYW1lIGZ1bmN0aW9uLCBhbmQgc2hvdWxkIGJlIGFibGUgdG8gZHJvcCB0aGUgbGFzdCBjb2x1bW4gKGdlb21ldHJ5LikNCmBgYHtyfQ0KbXNvYV9waXZvdF90aWJibGUgPC0gbXNvYV9waXZvdF90aWJibGVbMDozXQ0KbXNvYV9waXZvdF90aWJibGUNCmBgYA0KV2Ugbm93IG5lZWQgdG8gZmlsbCBvdXIgbWlzc2luZyB2YWx1ZXMuICBSYXRoZXIgdGhhbiBpdGVyYXRlIG9yIHNpbWlsYXIsIEknbGwganVzdCBhZGQgYW4gZW50aXJlIGRmIGZpbGxlZCB3aXRoIDBzIGZvciBib3RoIGNyaW1lIHR5cGVzLCB0aGVuIGRyb3AgYW55IGR1cGxpY2F0ZXMgLSBhdCBsZWFzdCwgdGhhdCdzIGhvdyBJJ2QgZG8gaXQgaW4gUHl0aG9uLCBhbmQgc2hhbGwgdHJ5IHRvIGRvIGhlcmUhDQoNCmBgYHtyfQ0KI2NyZWF0aW5nIGEgZGYgd2l0aCBhbGwgbXNvYSBuYW1lcywgZm9yIHJvYmJlcnkgYW5kIGJ1cmdsYXJ5DQptc29hX3plcm9fZGZfcm9iYmVyeSA8LSB1bmlxdWUoYXNfdGliYmxlKGxzb2FfYm9yZGVycylbIk1TT0ExMUNEIl0pDQptc29hX3plcm9fZGZfYnVyZ2xhcnkgPC0gdW5pcXVlKGFzX3RpYmJsZShsc29hX2JvcmRlcnMpWyJNU09BMTFDRCJdKQ0KDQojYWRkaW5nIG91ciBjcmltZSB0eXBlIGNvbHVtbiANCm1zb2FfemVyb19kZl9idXJnbGFyeVsiQ3JpbWUudHlwZSJdID0gIkJ1cmdsYXJ5Ig0KbXNvYV96ZXJvX2RmX3JvYmJlcnlbIkNyaW1lLnR5cGUiXSA9ICJSb2JiZXJ5Ig0KDQojQ3JlYXRpbmcgYSAiY291bnQiIGNvbHVtbiBpZGVudGljYWwgdG8gb3VyIHBpdm90LCBhbmQgZmlsbGluZyBpdCB3aXRoIDANCm1zb2FfemVyb19kZl9idXJnbGFyeVsiY291bnRfYnlfbXNvYSJdID0gYXMubnVtZXJpYygwKQ0KbXNvYV96ZXJvX2RmX3JvYmJlcnlbImNvdW50X2J5X21zb2EiXSA9IGFzLm51bWVyaWMoMCkNCg0KbXNvYV96ZXJvX2RmX3JvYmJlcnkNCmBgYA0KSSdtIGEgbGl0dGxlIHdvcnJpZWQgYWJvdXQgdGhlICJkYmwiIGNsYXNzLCBidXQgbGV0J3MgaWdub3JlIHRoYXQgZm9yIG5vdy4gIE5vdywgd2UgbmVlZCB0byBjb25jYXRlbmF0ZSBib3RoLCBhbmQgYWRkIHRoZW0gdG8gb3VyIE1TT0EgcGl2b3QuDQoNCmBgYHtyfQ0KZHVwbGljYXRlX2NvbmNhdCA8LSByYmluZChtc29hX3plcm9fZGZfcm9iYmVyeSwgbXNvYV96ZXJvX2RmX2J1cmdsYXJ5KQ0KZHVwbGljYXRlX2NvbmNhdA0KYGBgDQoNCkl0IHNlZW1zIHRvIGhhdmUgd29ya2VkLiAgVGhhdCBzYWlkLCB0aGUgZmFjdCB0aGVyZSBpcyBubyBjbGVhciBmdW5jdGlvbiBmb3IgY29uY2F0ZW5hdGlvbiAoaW4gY29udHJhc3QgdG8gcGQuY29uY2F0ZW5hdGUgaW4gUGFuZGFzKSBzdXJwcmlzZXMgbWUuICBOb3csIGxldCdzIGZpbmFsbHkgY29uY2F0ZW5hdGUgZXZlcnl0aGluZywgYW5kIHJlbW92ZSBkdXBsaWNhdGVzLiAgVGhhdCBzaG91bGQgZm9ybSBvdXIgZmluYWwgbW9udGhseSBkZiwgYW5kIHdlIGNhbiB0aGVuIGNvbWJpbmUgYWxsIG91ciBwcmV2aW91cyBzdGVwcyBpbnRvIGEgZnVuY3Rpb24uDQoNCmBgYHtyfQ0KDQpkZl93aXRoX2R1cHMgPC0gcmJpbmQobXNvYV9waXZvdF90aWJibGUsIGR1cGxpY2F0ZV9jb25jYXQpDQoNCmRmX3dpdGhfZHVwcw0KYGBgDQpJdCdzIG5vdGljZWFibGUgaG93IG11Y2ggaGFyZGVyIGZpbmRpbmcgZG9jdW1lbnRhdGlvbiBpcyBmb3IgUiB0aGFuIFBhbmRhcyAtIHdoaWxlIHRoZSBkcm9wX2R1cGxpY2F0ZXMgZnVuY3Rpb24gaXMgZnJvbnQgYW5kIGNlbnRlciBmb3IgYW55IHNlYXJjaGVzIGluIFB5dGhvbiwgYSBzaW1pbGFyIHNlYXJjaCBpbiBSIHJldmVhbHMgcGxlbnR5IG9mIGhhY2t5IGZpbHRlcnMsICBidXQgdGhlICJkaXN0aW5jdCIgZnVuY3Rpb24gc2VlbXMgdG8gYmUgd2hhdCBJJ20gYWN0dWFsbHkgbG9va2luZyBmb3IuDQpgYGB7cn0NCiNjcmVhdGluZyBhIGZpbHRlciBmb3IgZHVwbGljYXRlcyBjb2x1bW5zLCB3aGljaCBzaG91bGQgaWdub3JlIHRoZSBmaXJzdCBpbnN0YW5jZQ0KZHVwX2ZpbHRlcnMgPC0gZHVwbGljYXRlZChkZl93aXRoX2R1cHNbMDoyXSkNCg0KDQptb250aGx5X2RmIDwtIGZpbHRlcihkZl93aXRoX2R1cHMsICFkdXBfZmlsdGVycykNCm1vbnRobHlfZGYNCmBgYA0KVGhhdCBzaG91bGQgbm93IGJlIGFsbCBvdXIgdmFsdWVzLiAgQXMgYSBzYW5pdHkgY2hlY2ssIGxldCdzIG1ha2Ugc3VyZSB3ZSBoYXZlIHRoZSByaWdodCBudW1iZXIgb2Ygcm93cywgdXNpbmcgUidzICJkaW0iIGZ1bmN0aW9uIChlcXVpdmFsZW50IHRvIHNoYXBlIGluIFBhbmRhcykgdG8gY2hlY2sgaG93IG1hbnkgdW5pcXVlIHZhbHVlcyB3ZSB3b3VsZCBleHBlY3QuDQpgYGB7cn0NCmRpbSh1bmlxdWUoYXNfdGliYmxlKGxzb2FfYm9yZGVycylbIk1TT0ExMUNEIl0pKVsxXSAqIDINCmBgYA0KV2UndmUgZ290IDIgZXh0cmEuLi5hIGJpdCB3ZWlyZCwgYnV0IG5vdCBlbmQgb2Ygd29ybGQuICBMZXQncyBsZWF2ZSBpdCBhdCB0aGF0Lg0KDQpXZSBub3cgbmVlZCB0byBhZGQgb3VyIG1vbnRobHkgZGF0ZSB0byB0aGlzIGRhdGFmcmFtZQ0KDQpgYGB7cn0NCiNzZWxlY3QgdGhlIGZpcnN0IHVuaXF1ZSB2YWx1ZSBvZiBtb250aHMgaW4gdGhlIG9yaWdpbmFsIGRhdGFmcmFtZQ0KbW9udGggPC0gdW5pcXVlKHRlc3RfZGZbIk1vbnRoIl0pWzEsMV0NCm1vbnRobHlfZGZbIk1vbnRoIl0gPC0gbW9udGgNCm1vbnRobHlfZGYNCmBgYA0KTm93LCBsZXQncyBicmluZyBhbGwgb3VyIHByZXZpb3VzIHdvcmsgdG9nZXRoZXIgaW50byBhIGZ1bmN0aW9uIChhbmQgZml4IG15IGF3a3dhcmQgcHJpb3IgbXNvYS9sc29hIHR5cG8pDQoNCmBgYHtyfQ0KI3F1aWNrIGluaXRpYWwgZnVuY3Rpb24gdG8gZ2VuZXJhdGUgb3VyIE1TT0EgYm9yZGUgc3BhdGlhbCBmcmFtZSwgdG8gYXZvaWQgaXQgc2l0dGluZyBpbiB0aGUgaW5pdGlhbCBmcmFtZSBhbmQgZ29iYmxpbmcgbG9hZHMgb2YgbWVtb3J5Lg0KZ2VuZXJhdGVfbXNvYV9ib3JkZXJzIDwtIGZ1bmN0aW9uKGZpbGUpew0KICBtc29hX2JvcmRlcnMgPC0gc3RfcmVhZChmaWxlLCBjcnM9Mjc3MDApDQogIHJldHVybihtc29hX2JvcmRlcnMpDQp9DQoNCm1ha2VfbW9udGhfcGl2b3QgPC0gZnVuY3Rpb24oZmlsZSl7DQogICNkZWZpbmUgb3VyIENSUw0KICBsYXRsb25nID0gIitpbml0PWVwc2c6NDMyNiINCiAgdWtncmlkID0gIitpbml0PWVwc2c6Mjc3MDAiDQogICNyZWFkIG91ciBjcmltZSBmcm9tIHRoZSBmaWxlDQogIHRlc3RfZGYgPC0gcmVhZC5jc3YoZmlsZSkNCiAgI3NlbGVjdCBvbmx5IG91ciB0YXJnZXQgY3JpbWUgdHlwZXMNCiAgc3Vic2V0X2RmIDwtIGZpbHRlcih0ZXN0X2RmLCBDcmltZS50eXBlPT0iQnVyZ2xhcnkiIHwgQ3JpbWUudHlwZT09IlJvYmJlcnkiKQ0KICAjcmVtb3ZlIGFueSByb3dzIHdpdGggYSBsb25nL2xhdCBjb29yZGluYXRlDQogIGNsZWFuX2RmIDwtIHN1YnNldF9kZlshcm93U3Vtcyhpcy5uYShzdWJzZXRfZGZbIkxvbmdpdHVkZSJdKSksIF0NCiAgI2dlbmVyYXRlIGEgc3BhdGlhbCBkZg0KICBzdWJzZXRfc3BhdGlhbCA8LSBzdF9hc19zZihjbGVhbl9kZiwgY29vcmRzID0gYygiTG9uZ2l0dWRlIiwgIkxhdGl0dWRlIiksIA0KICAgICAgICAgICAgICAgICAgICAgIGNycyA9IDQzMjYsIHJlbW92ZSA9IEZBTFNFKQ0KICAjcmVwcm9qZWN0IHRvIHVrIGdyaWQgY29vcmRzDQogIHN1YnNldF9vc2diIDwtIHN0X3RyYW5zZm9ybShzdWJzZXRfc3BhdGlhbCwgdWtncmlkKQ0KICAjc3BhdGlhbGx5IGpvaW4gdG8gYXNzaWduIHRvIGFuIE1TT0ENCiAgY3JpbWVfd2l0aF9tc29hIDwtIHN0X2pvaW4oc3Vic2V0X29zZ2IsIG1zb2FfYm9yZGVyc1siTVNPQTExQ0QiXSkNCiAgI3N1bW1hcmlzZSBieSBjb3VudCBvZiBNU09BDQogIG1zb2FfbGlzdDwtIGNyaW1lX3dpdGhfbXNvYSAlPiUNCiAgICBncm91cF9ieShNU09BMTFDRCwgQ3JpbWUudHlwZSkgJT4lDQogICAgc3VtbWFyaXplKGNvdW50X2J5X21zb2EgPSBuKCkpDQogICNyZXR1cm4gdG8gYSBub24tZ2VvZ3JhcGhpYyBtc29hDQogIG1zb2FfcGl2b3RfdGliYmxlIDwtIGFzX3RpYmJsZShtc29hX2xpc3QpDQogIG1zb2FfcGl2b3RfdGliYmxlIDwtIG1zb2FfcGl2b3RfdGliYmxlWzA6M10NCiAgI2NyZWF0aW5nIGEgZGYgd2l0aCBhbGwgbXNvYSBuYW1lcywgZm9yIHJvYmJlcnkgYW5kIGJ1cmdsYXJ5DQogIG1zb2FfemVyb19kZl9yb2JiZXJ5IDwtIHVuaXF1ZShhc190aWJibGUobXNvYV9ib3JkZXJzKVsiTVNPQTExQ0QiXSkNCiAgbXNvYV96ZXJvX2RmX2J1cmdsYXJ5IDwtIHVuaXF1ZShhc190aWJibGUobXNvYV9ib3JkZXJzKVsiTVNPQTExQ0QiXSkNCiAgI2FkZGluZyBvdXIgY3JpbWUgdHlwZSBjb2x1bW4gDQogIG1zb2FfemVyb19kZl9idXJnbGFyeVsiQ3JpbWUudHlwZSJdID0gIkJ1cmdsYXJ5Ig0KICBtc29hX3plcm9fZGZfcm9iYmVyeVsiQ3JpbWUudHlwZSJdID0gIlJvYmJlcnkiDQogICNDcmVhdGluZyBhICJjb3VudCIgY29sdW1uIGlkZW50aWNhbCB0byBvdXIgcGl2b3QsIGFuZCBmaWxsaW5nIGl0IHdpdGggMA0KICBtc29hX3plcm9fZGZfYnVyZ2xhcnlbImNvdW50X2J5X21zb2EiXSA9IGFzLm51bWVyaWMoMCkNCiAgbXNvYV96ZXJvX2RmX3JvYmJlcnlbImNvdW50X2J5X21zb2EiXSA9IGFzLm51bWVyaWMoMCkNCiAgZHVwbGljYXRlX2NvbmNhdCA8LSByYmluZChtc29hX3plcm9fZGZfcm9iYmVyeSwgbXNvYV96ZXJvX2RmX2J1cmdsYXJ5KQ0KICBkZl93aXRoX2R1cHMgPC0gcmJpbmQobXNvYV9waXZvdF90aWJibGUsIGR1cGxpY2F0ZV9jb25jYXQpDQogICNjcmVhdGluZyBhIGZpbHRlciBmb3IgZHVwbGljYXRlcyBjb2x1bW5zLCB3aGljaCBzaG91bGQgaWdub3JlIHRoZSBmaXJzdCBpbnN0YW5jZQ0KICBkdXBfZmlsdGVycyA8LSBkdXBsaWNhdGVkKGRmX3dpdGhfZHVwc1swOjJdKQ0KICBtb250aGx5X2RmIDwtIGZpbHRlcihkZl93aXRoX2R1cHMsICFkdXBfZmlsdGVycykNCiAgI3JlLWFkZCBvdXIgbW9udGggY29sdW1uDQogIG1vbnRoIDwtIHVuaXF1ZSh0ZXN0X2RmWyJNb250aCJdKVsxLDFdDQogIG1vbnRobHlfZGZbIk1vbnRoIl0gPC0gbW9udGgNCiAgcmV0dXJuKG1vbnRobHlfZGYpDQp9DQpgYGANCg0KV2UndmUgbm93IGdvdCBvdXIgdG9vbGluZyBmb3IgYSBkYXRhIHBpcGVsaW5lIHJlYWR5IHRvIGdvISAgV2UgY2FuIG5vdyBydW4gdGhpcyBvbiBldmVyeSBzaW5nbGUgbW9udGggb2YgZGF0YSwgYW5kIGFnZ3JlZ2F0ZSBpbnRvIGEgaGlzdG9yaWNhbCBjb21iaW5lZCBkYXRhLXNldC4NCg0KRGF0YS5Qb2xpY2UuVUsgY29tZXMgYXMgYSBidW5jaCBvZiBuZXN0ZWQtc3ViZGlyZWN0b3JpZXMuLi5pbiBoaW5kc2lnaHQsIEkgcHJvYmFibHkgc2hvdWxkIGhhdmUgbG9va2VkIGF0IHRoZWlyIEFQSSwgYnV0IGZvciBub3cgbGV0J3MgcG93ZXIgYWhlYWQgYW5kIGZpZ3VyZSBvdXQgaG93IHRvIGV4dHJhY3QgYSBsaXN0IG9mIGFsbCB0aGUgQ1NWIGZpbGVzIGluIG91ciBmb2xkZXIgYW5kIHRoZSB2YXJpb3VzIHN1Yi1kaXJlY3Rvcmllcy4NCg0KYGBge3J9DQpsaXN0LmZpbGVzKHBhdGggPSAiY3JpbWVzIikNCg0KYGBgDQpBcyB3ZSBzdXNwZWN0ZWQsIHRoZSBuZXN0ZWQgZGlyZWN0b3JpZXMgY2F1c2UgYW4gaXNzdWUgLSBndWVzcyB3ZSdyZSBnb2luZyB0byBoYXZlIHRvIGxlYXJuIGFib3V0IGxvb3BzIGluIFIhICBMZXQncyBpdGVyYXRlIG92ZXIgb3VyIGxpc3Qgb2Ygc3ViZm9sZGVycywgYW5kIHJlLWFwcGx5IHRoZSBmdW5jdGlvbiB0byBlYWNoLg0KYGBge3J9DQpzdWJmb2xkZXJzIDwtIGxpc3QuZmlsZXMocGF0aCA9ICJjcmltZXMiKQ0KZmlsZV9saXN0IDwtIGxpc3QoKQ0KZm9yIChmb2xkZXIgaW4gc3ViZm9sZGVycyl7DQogIGZvbGRlcl9zdWJkaXIgPC0gImNyaW1lcy8iDQogICNjb25jYXRlbmF0ZSB0byBnZXQgb3VyIHRvdGFsIHN1YmZvbGRlciBkaXJlY3RvcnkgLSBoYWNreSBidXQgd2lsbCB3b3JrIGhlcmUuDQogIHN1Yl9wYXRoIDwtIHBhc3RlKGZvbGRlcl9zdWJkaXIsIGZvbGRlciwgc2VwPSIiKQ0KICBsaXN0LmZpbGVzKHN1Yl9wYXRoKQ0KICBmaWxlX2xpc3QgPC0gbGlzdChmaWxlX2xpc3QsIHBhc3RlKHN1Yl9wYXRoLCIvIiwgbGlzdC5maWxlcyhzdWJfcGF0aCksIHNlcD0iIikpDQogIH0NCg0KYGBgDQoNCg0KVGhlIGJhZCBuZXdzIGlzLCB0aGlzIHRvdGFsbHkgZGlkbid0IHdvcmsuIFRoZSBnb29kIG5ld3MgaXMsIGl0IGxlZCBtZSB0byB0aGUgZmFyIGNsZWFuZXIsICJyZWN1cnNpdmUiIHZlcnNpb24gb2YgdGhlIHJlYWQgZmlsZXMgZnVuY3Rpb24uDQoNCmBgYHtyfQ0KbGlzdC5maWxlcyhwYXRoID0gImNyaW1lcyIsIHJlY3Vyc2l2ZT1UKQ0KDQpgYGANCkZpcnN0LCBsZXQncyBjcmVhdGUgYW4gZW1wdHkgZGF0YWZyYW1lIHdlIGNhbiBjb25jYXRlbmF0ZSBhbGwgdGVoIHJlc3QgdG8uICANCmBgYHtyfQ0KZW1wdHlfZGYgPC0gdGliYmxlKA0KTVNPQTExQ0QgPSAiIiwgDQpDcmltZS50eXBlPSAiIiwNCmNvdW50X2J5X21zb2E9ICIiLA0KTW9udGg9ICIiDQopDQplbXB0eV9kZg0KYGBgDQpBbm5veWluZ2x5LCBJIGRvbid0IHNlZW0gdG8gYmUgYWJsZSB0byBjcg0KYGBge3J9DQptc29hX2JvcmRlcnMgPC0gZ2VuZXJhdGVfbXNvYV9ib3JkZXJzKCJtc29hX2JvcmRlcnMvTVNPQV8yMDExX0xvbmRvbl9nZW5fTUhXLnRhYiIpDQptc29hX2JvcmRlcnMNCmBgYA0KT3VyIE1TT0EgYm9yZGVyIGhlbHBlciBmdW5jdGlvbnMgc2VlbXMgdG8gd29yay4gIE5vdywgdGltZSB0byBkbyB0aGUgaGVhdnkgbGlmdGluZyENCg0KDQpgYGB7cn0NCnN1YmZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aCA9ICJjcmltZXMiLCByZWN1cnNpdmU9VCkNCg0KZm9yIChmaWxlIGluIHN1YmZpbGVzKXsNCiAgZm9sZGVyX3N1YmRpciA8LSAiY3JpbWVzLyINCiAgI2NvbmNhdGVuYXRlIHRvIGdldCBvdXIgdG90YWwgc3ViZm9sZGVyIGRpcmVjdG9yeSAtIGhhY2t5IGJ1dCB3aWxsIHdvcmsgaGVyZS4NCiAgc3ViX3BhdGggPC0gcGFzdGUoZm9sZGVyX3N1YmRpciwgZmlsZSwgc2VwPSIiKQ0KICBtb250aGx5X2RmIDwtIG1ha2VfbW9udGhfcGl2b3Qoc3ViX3BhdGgpDQogIGVtcHR5X2RmIDwtIHJiaW5kKGVtcHR5X2RmLCBtb250aGx5X2RmKQ0KfQ0KDQpgYGANClRoYXQgc2VlbXMgdG8gaGF2ZSB3b3JrZWQhIFRoZSBwcm9jZXNzaW5nIHRpbWUgd2FzIGxvbmdlciB0aGFuIEkgZXhwZWN0ZWQgKHdoaWNoIGlzIHByb2JhYmx5IHNvbWV0aGluZyB0byBkbyB3aXRoIGhvdyBSIHN0b3JlcyBtZW1vcnkpIC0gbGV0J3MgbG9vayBhdCBvdXIgcHJldmlvdXNseSBlbXB0eSBkYXRhZnJhbWUuDQpgYGB7cn0NCmVtcHR5X2RmDQpgYGANCg0KDQpgYGB7cn0NCnVuaXF1ZShlbXB0eV9kZlsiTW9udGgiXSkNCmBgYA0KDQpTbyB3ZSBub3cgaGF2ZSBhIGNvbWJpbmVkIGRhdGFmcmFtZSBvZiBqdXN0IHVuZGVyIDcxLDAwMCByb3dzLCBmb3IgMzcgaW5kaXZpZHVhbCBtb250aHMgYmVldHdlZW4gSmFudWFyeSAyMDE4IGFuZCBEZWNlbWJlciAyMDIwLCBmb3IgZXZlcnkgcm9iYmVyeSBhbmQgYnVyZ2xhcnkgaW4gTG9uZG9uLCBhc3NpZ25lZCB0byBhbiBNU09BLiAgSSdkIGNhbGwgdGhhdCBhIHdpbiEgIA0KDQpUaGlzIGhhcyBiZWVuIHNvbWV3aGF0IG1vcmUgcGFpbmZ1bCB0aGFuIEkgZXhwZWN0ZWQsIHNvIGJlZm9yZSBnb2luZyBhbnkgZnVydGhlciwgbGV0J3MgZmlndXJlIG91dCBob3cgdG8gc2F2ZSB0aGlzIGZpbGUuICBHaXZlbiBpdCdzIGFsbCBzdHJpbmdzIGFuZCBpbnRlZ2VycywgYSBzaW1wbGUgQ1NWIHNob3VsZCBkbyBmb3Igbm93Lg0KDQpgYGB7cn0NCndyaXRlLmNzdihlbXB0eV9kZiwibXNvYV9jcmltZV9tYXRyaXguY3N2IikNCg0KYGBgDQoNCiMjIE1vZGVsbGluZyAtIENyaW1lIFRyZW5kcyBieSBNU09BDQpXZSBjYW4gbm93IG1vdmUgb24gdG8gdGhlIGZ1biBiaXQgLSBwcmVkaWN0aW5nIHRoZSBjcmltZSB0cmVuZCB3ZSdkIGV4cGVjdCwgYW5kIHRoZW4gbG9va2luZyBhdCBob3cgbXVjaCBpdCBkaXZlcmdlcyB3aGVuIHdlIGhpdCB0aGUgInBhbmRlbWljIGRpc3J1cHRpb24iIHBlcmlvZC4NCg0KSSdsbCBlaXRoZXIgYmUgdXNpbmcgYXV0by1hcmltYSBvciBGYWNlYm9vaydzIHByb3BoZXQgYWxnb3JpdGhtLCBib3RoIG9mIHdoaWNoIHByb2R1Y2UgcmVsYXRpdmVseSBhY2N1cmF0ZSBmb3JlY2FzdHMgd2l0aCBsaXR0bGUgbmVjZXNzYXJ5IHR1bmluZy4gIEknbSBob3BpbmcgMjAxOCB0aHJvdWdoIGVhcmx5IDIwMjAgc2hvdWxkIGJlIHN1ZmZpY2llbnQgdG8gZXN0YWJsaXNoIHRyZW5kcyBhbmQgc2Vhc29uYWxpdHkuIFdlIGNhbiB0aGVuIHVzZSBvdXIgZXJyb3IgcmF0ZSBmcm9tIE1hcmNoIDIwMjAgb253YXJkcyBhcyBhIG1lYXN1cmUgb2YgdGhlICJwYW5kZW1pYyBlZmZlY3QiLg0KDQpBcyBhIHRlc3QsIGxldCdzIHN0YXJ0IGJ5IHByZWRpY3Rpbmcgb25lIE1TT0E6IHRoZSBmaXJzdCBpbiBvdXIgZGYsICJFMDIwMDAwMDEiDQoNCmBgYHtyfQ0KZW1wdHlfZGYgPC0gcmVhZC5jc3YoIm1zb2FfY3JpbWVfbWF0cml4LmNzdiIpDQplbXB0eV9kZiA8LSBlbXB0eV9kZlsyOjcwODQ4LDI6NV0NCmVtcHR5X2RmDQpgYGANCg0KDQpgYGB7cn0NCnNpbmdsZV9tc29hX2RmIDwtIGZpbHRlcihlbXB0eV9kZiwgTVNPQTExQ0QgPT0gIkUwMjAwMDAwMSIgJiBDcmltZS50eXBlPT0iQnVyZ2xhcnkiKQ0Kc2luZ2xlX21zb2FfZGYNCmBgYA0KQXMgd2UnZCBleHBlY3QsIDM2IHJvd3MgZm9yIDM2IG1vbnRocy4gIExldCdzIGNvdmVydCB0aG9zZSByb3dzIHRvIGEgZGF0ZSwgYW5kIHN0YXJ0IG1ha2luZyBwcmVkaWN0aW9ucy4NCg0KYGBge3J9DQpzaW5nbGVfbXNvYV9kZiREYXRlU3RyaW5nIDwtIHBhc3RlKHNpbmdsZV9tc29hX2RmJE1vbnRoLCAiLTAxIikNCnNpbmdsZV9tc29hX2RmDQoNCmBgYA0KQ29udmVydGluZyB0aGVzZSB0byBkYXRlcyB3YXMgaGFyZGVyIHRoYW4gSSdkIGFudGljaXBhdGVkLCBidXQgdGhlIFRpZHl2ZXJzZSBlY29zeXN0ZW0gZG9lcyBoYXZlIHNvbWUgbmlmdHkgdG9vbHMhIA0KDQpgYGB7cn0NCmxpYnJhcnkobHVicmlkYXRlKQ0KDQpzaW5nbGVfbXNvYV9kZiREYXRlQ2xlYW4gPC0geW1kKHNpbmdsZV9tc29hX2RmJERhdGVTdHJpbmcpDQpzaW5nbGVfbXNvYV9kZg0KYGBgDQpMZXQncyBub3cgYnVpbGQgb3VyICJwcmUtcGFuZGVtaWMiIHRyYWluaW5nIHNldCwgdXAgdG8gRmVicnVhcnkgMjAyMCwgYW5kIHVzZSBwcm9waGV0IHRvIG1ha2Ugc29tZSBwcmVkaWN0aW9ucy4NCg0KYGBge3J9DQoNCnRyYWluaW5nX3NldCA8LSBmaWx0ZXIoc2luZ2xlX21zb2FfZGYsIERhdGVDbGVhbiA8ICIyMDIwLTAzLTAxIikNCnRyYWluaW5nX3NldA0KDQpgYGANCmBgYHtyfQ0KdHJhaW5pbmdfZGYgPC0gdGliYmxlKA0KICBkcz10cmFpbmluZ19zZXQkRGF0ZUNsZWFuLA0KICB5PXRyYWluaW5nX3NldCRjb3VudF9ieV9tc29hDQopDQp0cmFpbmluZ19kZg0KYGBgDQpXZSBjYW4gbm93IGluc3RhbnRpYXRlIG91ciBwcm9waGV0IG1vZGVsLCBhbmQgc3RhcnQgbWFraW5nIHByZWRpY3Rpb25zLg0KDQpgYGB7cn0NCmxpYnJhcnkocHJvcGhldCkNCm0gPC0gcHJvcGhldCh0cmFpbmluZ19kZikNCg0KYGBgDQpXZSdsbCBwcmVkaWN0IGZvciBhIHBlcmlvZCBvZiBhcm91bmQgMyBtb250aHMgIGFuZCB0aGVuIGNvbXBhcmUgdG8gd2hhdCBhY3R1YWxseSBoYXBwZW5lZC4NCmBgYHtyfQ0KZnV0dXJlIDwtIG1ha2VfZnV0dXJlX2RhdGFmcmFtZShtLCBwZXJpb2RzID0gNiwgZnJlcSA9ICdtb250aCcpDQp0YWlsKGZ1dHVyZSkNCmBgYA0KTGV0J3MgZ2V0IGZvcmVjYXN0aW5nDQpgYGB7cn0NCg0KZm9yZWNhc3QgPC0gcHJlZGljdChtLCBmdXR1cmUpDQp0YWlsKGZvcmVjYXN0W2MoJ2RzJywgJ3loYXQnLCAneWhhdF9sb3dlcicsICd5aGF0X3VwcGVyJyldKQ0KDQpgYGANCg0KYGBge3J9DQojIFINCnBsb3QobSwgZm9yZWNhc3QpDQoNCmBgYA0KYGBge3J9DQrimaAjIFINCnByb3BoZXRfcGxvdF9jb21wb25lbnRzKG0sIGZvcmVjYXN0KQ0KYGBgDQpUaGVzZSBwcmVkaWN0aW9ucyBvYnZpb3VzbHkgbG9vayBhIGxpdHRsZSBzaWxseSwgYnV0IHRoZSB5ZWFybHkgdHJlbmQgKHdoaWNoIGlzIHdoYXQgd2UgcmVhbGx5IHdhbnRlZCB0byBnZXQgb3V0IG9mIHRoaXMpIGRvZXNuJ3QgbG9vayBtYWQgdG8gbWUuIEknbSBob3BpbmcgdGhhdCB1c2luZyBhbGwgb3VyIE1TT0EgaW4gYWdncmVnYXRlLCB3ZSdsbCBnZXQgbWVhbmluZ2Z1bCBkYXRhLiAgRmlyc3RseSwgd2UgbmVlZCB0byBnZXQgb3VyIGVycm9yIHJhdGUuDQoNCmBgYHtyfQ0KDQpoZWFkKGZvcmVjYXN0KQ0KDQpmb3JlY2FzdCRNb250aCA8LSBtb250aChmb3JlY2FzdCRkcykNCmZvcmVjYXN0JFllYXIgPC0geWVhcihmb3JlY2FzdCRkcykNCg0KZm9yZWNhc3QNCg0KYGBgDQoNClRvIGRvIG5leHQuDQoNCkdyb3VwIHByZWRpY3Rpb24gYnkgbW9udGgNCldlJ2xsIGxvb2sgYXQgQXByaWwgYW5kIE1heSwgd2hpY2ggd2VyZSAicGVhayIgTG9uZG9uIENPVklEIGVmZmVjdA0KYGBge3J9DQpmb3JlY2FzdA0KYGBgDQoNCmBgYHtyfQ0KdGhpc195ZWFyIDwtIGZpbHRlcihmb3JlY2FzdCwgWWVhciA+IDIwMTkpDQpwZWFrX3BhbmRlbWljIDwtIGZpbHRlcih0aGlzX3llYXIsIE1vbnRoPT0gNCB8IE1vbnRoPT0gNSApDQpwZWFrX3BhbmRlbWljDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uUGl2b3QgPC0gcGVha19wYW5kZW1pYyAlPiUNCiAgZ3JvdXBfYnkoTW9udGgpICU+JQ0KICBzdW1tYXJpemUocHJlZGljdGVkX2J1cmdsYXJ5ID0gbWVhbih5aGF0KSkNCg0KcHJlZGljdGlvblBpdm90DQoNCmBgYA0KTm93LCBsZXQncyBjb21wYXJlIHRoYXQgdG8gb3VyIEFDVFVBTCBkYXRhLCBhbmQgZ2V0IGFuIGVycm9yIHJhdGUuDQoNCmBgYHtyfQ0KDQoNCg0Kc2luZ2xlX21zb2FfZGYkTW9udGhOdW0gPC0gbW9udGgoc2luZ2xlX21zb2FfZGYkRGF0ZUNsZWFuKQ0Kc2luZ2xlX21zb2FfZGYkWWVhck51bSA8LSB5ZWFyKHNpbmdsZV9tc29hX2RmJERhdGVDbGVhbikNCg0KdGhpc195ZWFyX2FjdHVhbCA8LSBmaWx0ZXIoc2luZ2xlX21zb2FfZGYsIFllYXJOdW0gPiAyMDE5KQ0KcGVha19wYW5kZW1pY19hY3R1YWwgPC0gZmlsdGVyKHRoaXNfeWVhcl9hY3R1YWwsIE1vbnRoTnVtPT0gNCB8IE1vbnRoTnVtPT0gNSApDQpwZWFrX3BhbmRlbWljX2FjdHVhbA0KYGBgDQoNCkFzIHN1Y2gsIG91ciBtb2RlbCBhbnRpY2lwYXRlZCBhcm91bmQgMTggYnVyZ2xhcmllcyBkdXJpbmcgdGhpcyBwZXJpb2QsIHdoaWxlIGluIHJlYWxpdHksIHRoZXJlIHdhcyAxLiBUaGlzIGlzIHRoZSBtZWFzdXJlIG9mIG91ciAiY292aWQgZXJyb3IiIGZvciB0aGUgTVNPQS4gIExldCdzIGdldCB0aGF0IG1lYXN1cmUgaW50byBhIHBlcmNlbnRhZ2UgZXJyb3IgYXMgd2VsbCBhcyBhYnNvbHV0ZSBlcnJvciwgYW5kIHRoZW4gd2UgY2FuIHJlcGVhdCB0aGUgcHJvY2VzcyBmb3IgYWxsIG9mIExvbmRvbg0KDQpgYGB7cn0NCmFjdHVhbF9idXJnbGFyeSA8LSBzdW0ocGVha19wYW5kZW1pY19hY3R1YWwkY291bnRfYnlfbXNvYSkNCnByZWRfYnVyZ2xhcnkgPC0gc3VtKHByZWRpY3Rpb25QaXZvdCRwcmVkaWN0ZWRfYnVyZ2xhcnkpDQoNCmVycm9yIDwtIGFjdHVhbF9idXJnbGFyeSAtIHByZWRfYnVyZ2xhcnkNCnBlcmNlbnRhZ2VfZXJyb3IgPC0gZXJyb3IgLyBwcmVkX2J1cmdsYXJ5IA0KDQpwcmludCgiQnVyZ2xhcnkgQ291bnQiKQ0KcHJpbnQoYWN0dWFsX2J1cmdsYXJ5KQ0KcHJpbnQoIlByZWRpY3RlZCIpDQpwcmludChwcmVkX2J1cmdsYXJ5KQ0KDQpwcmludCgiQWN0dWFsIEVycm9yIikNCnByaW50KGVycm9yKQ0KcHJpbnQoIlBlcmNlbnRhZ2UgRXJyb3IiKQ0KcHJpbnQocGVyY2VudGFnZV9lcnJvcikNCmBgYA0KTm93LCBsZXQncyBhdXRvbWF0ZS4gU2ltaWxhciB0byBvdXIgcHJldmlvdXMgcHJvY2Vzcywgd2UnbGwgY3JlYXRlIGEgZGF0YWZyYW1lIGZvciBldmVyeSBNU09BLCBhbmQgYm90aCB0eXBlcywgdGhlbiBydW4gYSBmb3IgbG9vcCByZXBlYXRpbmcgb3VyIHByb2Nlc3MgYWNyb3NzIExvbmRvbi4NCmBgYHtyfQ0KDQoNCm1zb2FfZXJyb3JfdGliYmxlIDwtIHRpYmJsZSgNCk1TT0ExMUNEID0gIiIsIA0KYnVyZ2xhcnlBY3R1YWw9ICIiLA0KYnVyZ2xhcnlQcmVkaWN0ZWQ9ICIiLA0KYnVyZ2xhcnlFcnJvcj0gIiIsDQpidXJnbGFyeVBlcmNlbnRFcnJvcj0iIiwNCnJvYmJlcnlBY3R1YWw9ICIiLA0Kcm9iYmVyeVByZWRpY3RlZD0gIiIsDQpyb2JiZXJ5RXJyb3I9ICIiLA0Kcm9iYmVyeVBlcmNlbnRFcnJvcj0iIg0KKQ0KDQptc29hX2Vycm9yX3RpYmJsZQ0KYGBgDQoNCmBgYHtyfQ0KY2FsY3VsYXRlX2Vycm9yIDwtIGZ1bmN0aW9uKG1zb2FOYW1lKXsNCiAgI3NlbGVjdCBvbmx5IGJ1cmdsYXJ5IGFuZCBvdXIgbXNvYQ0KICBzaW5nbGVfbXNvYV9kZiA8LSBmaWx0ZXIoZW1wdHlfZGYsIE1TT0ExMUNEID09IG1zb2FOYW1lICYgQ3JpbWUudHlwZT09IkJ1cmdsYXJ5IikNCiAgI2NsZWFuIGRhdGUgZGF0ZQ0KICBzaW5nbGVfbXNvYV9kZiREYXRlU3RyaW5nIDwtIHBhc3RlKHNpbmdsZV9tc29hX2RmJE1vbnRoLCAiLTAxIikNCiAgc2luZ2xlX21zb2FfZGYkRGF0ZUNsZWFuIDwtIHltZChzaW5nbGVfbXNvYV9kZiREYXRlU3RyaW5nKQ0KICAjZ2VuZXJhdGUgdHJhaW5pbmcgc2V0IHVwIHVudGlsIE1hcmNoDQogIHRyYWluaW5nX3NldCA8LSBmaWx0ZXIoc2luZ2xlX21zb2FfZGYsIERhdGVDbGVhbiA8ICIyMDIwLTAzLTAxIikNCiAgI3ByZXBhcmUgZm9yIFByb3BoZXQNCiAgdHJhaW5pbmdfZGYgPC0gdGliYmxlKA0KICAgIGRzPXRyYWluaW5nX3NldCREYXRlQ2xlYW4sDQogICAgeT10cmFpbmluZ19zZXQkY291bnRfYnlfbXNvYSkNCiAgI3N0YXJ0IGFuZCBwcmVkaWN0IHByb3BoZXQgZm9yIDYgbW9udGhzDQogIG0gPC0gcHJvcGhldCh0cmFpbmluZ19kZikNCiAgZnV0dXJlIDwtIG1ha2VfZnV0dXJlX2RhdGFmcmFtZShtLCBwZXJpb2RzID0gNiwgZnJlcSA9ICdtb250aCcpDQogIGZvcmVjYXN0IDwtIHByZWRpY3QobSwgZnV0dXJlKQ0KICBmb3JlY2FzdCRNb250aCA8LSBtb250aChmb3JlY2FzdCRkcykNCiAgZm9yZWNhc3QkWWVhciA8LSB5ZWFyKGZvcmVjYXN0JGRzKQ0KICAjYWdncmVnYXRlIGZvcmVjYXN0cyBhbmQgYWN0dWFsIGNyaW1lDQogIHRoaXNfeWVhciA8LSBmaWx0ZXIoZm9yZWNhc3QsIFllYXIgPiAyMDE5KQ0KICBwZWFrX3BhbmRlbWljIDwtIGZpbHRlcih0aGlzX3llYXIsIE1vbnRoPT0gNCB8IE1vbnRoPT0gNSApDQogIHByZWRpY3Rpb25QaXZvdCA8LSBwZWFrX3BhbmRlbWljICU+JQ0KICAgIGdyb3VwX2J5KE1vbnRoKSAlPiUNCiAgICBzdW1tYXJpemUocHJlZGljdGVkX2J1cmdsYXJ5ID0gbWVhbih5aGF0KSkNCg0KICBzaW5nbGVfbXNvYV9kZiRNb250aE51bSA8LSBtb250aChzaW5nbGVfbXNvYV9kZiREYXRlQ2xlYW4pDQogIHNpbmdsZV9tc29hX2RmJFllYXJOdW0gPC0geWVhcihzaW5nbGVfbXNvYV9kZiREYXRlQ2xlYW4pDQogICNnZW5lcmF0ZSBlcnJvciByYXRlcw0KICB0aGlzX3llYXJfYWN0dWFsIDwtIGZpbHRlcihzaW5nbGVfbXNvYV9kZiwgWWVhck51bSA+IDIwMTkpDQogIHBlYWtfcGFuZGVtaWNfYWN0dWFsIDwtIGZpbHRlcih0aGlzX3llYXJfYWN0dWFsLCBNb250aE51bT09IDQgfCBNb250aE51bT09IDUgKQ0KICBhY3R1YWxfYnVyZ2xhcnkgPC0gc3VtKHBlYWtfcGFuZGVtaWNfYWN0dWFsJGNvdW50X2J5X21zb2EpDQogIHByZWRfYnVyZ2xhcnkgPC0gc3VtKHByZWRpY3Rpb25QaXZvdCRwcmVkaWN0ZWRfYnVyZ2xhcnkpDQogIGVycm9yX2J1cmcgPC0gYWN0dWFsX2J1cmdsYXJ5IC0gcHJlZF9idXJnbGFyeQ0KICBwZXJjZW50YWdlX2Vycm9yX2J1cmcgPC0gZXJyb3JfYnVyZyAvIHByZWRfYnVyZ2xhcnkgDQogIA0KICAjbm93IHJlcGVhdCBmb3Igcm9iYmVyeQ0KICANCiAgc2luZ2xlX21zb2FfZGYgPC0gZmlsdGVyKGVtcHR5X2RmLCBNU09BMTFDRCA9PSBtc29hTmFtZSAmIENyaW1lLnR5cGU9PSJSb2JiZXJ5IikNCiAgc2luZ2xlX21zb2FfZGYkRGF0ZVN0cmluZyA8LSBwYXN0ZShzaW5nbGVfbXNvYV9kZiRNb250aCwgIi0wMSIpDQogIHNpbmdsZV9tc29hX2RmJERhdGVDbGVhbiA8LSB5bWQoc2luZ2xlX21zb2FfZGYkRGF0ZVN0cmluZykNCiAgdHJhaW5pbmdfc2V0IDwtIGZpbHRlcihzaW5nbGVfbXNvYV9kZiwgRGF0ZUNsZWFuIDwgIjIwMjAtMDMtMDEiKQ0KICB0cmFpbmluZ19kZiA8LSB0aWJibGUoDQogICAgZHM9dHJhaW5pbmdfc2V0JERhdGVDbGVhbiwNCiAgICB5PXRyYWluaW5nX3NldCRjb3VudF9ieV9tc29hKQ0KICBtIDwtIHByb3BoZXQodHJhaW5pbmdfZGYpDQogIGZ1dHVyZSA8LSBtYWtlX2Z1dHVyZV9kYXRhZnJhbWUobSwgcGVyaW9kcyA9IDYsIGZyZXEgPSAnbW9udGgnKQ0KICBmb3JlY2FzdCA8LSBwcmVkaWN0KG0sIGZ1dHVyZSkNCiAgZm9yZWNhc3QkTW9udGggPC0gbW9udGgoZm9yZWNhc3QkZHMpDQogIGZvcmVjYXN0JFllYXIgPC0geWVhcihmb3JlY2FzdCRkcykNCiAgdGhpc195ZWFyIDwtIGZpbHRlcihmb3JlY2FzdCwgWWVhciA+IDIwMTkpDQogIHBlYWtfcGFuZGVtaWMgPC0gZmlsdGVyKHRoaXNfeWVhciwgTW9udGg9PSA0IHwgTW9udGg9PSA1ICkNCiAgcHJlZGljdGlvblBpdm90IDwtIHBlYWtfcGFuZGVtaWMgJT4lDQogICAgZ3JvdXBfYnkoTW9udGgpICU+JQ0KICAgIHN1bW1hcml6ZShwcmVkaWN0ZWRfYnVyZ2xhcnkgPSBtZWFuKHloYXQpKQ0KDQogIHNpbmdsZV9tc29hX2RmJE1vbnRoTnVtIDwtIG1vbnRoKHNpbmdsZV9tc29hX2RmJERhdGVDbGVhbikNCiAgc2luZ2xlX21zb2FfZGYkWWVhck51bSA8LSB5ZWFyKHNpbmdsZV9tc29hX2RmJERhdGVDbGVhbikNCg0KICB0aGlzX3llYXJfYWN0dWFsIDwtIGZpbHRlcihzaW5nbGVfbXNvYV9kZiwgWWVhck51bSA+IDIwMTkpDQogIHBlYWtfcGFuZGVtaWNfYWN0dWFsIDwtIGZpbHRlcih0aGlzX3llYXJfYWN0dWFsLCBNb250aE51bT09IDQgfCBNb250aE51bT09IDUgKQ0KICBhY3R1YWxfcm9iYmVyeSA8LSBzdW0ocGVha19wYW5kZW1pY19hY3R1YWwkY291bnRfYnlfbXNvYSkNCiAgcHJlZF9yb2JiZXJ5IDwtIHN1bShwcmVkaWN0aW9uUGl2b3QkcHJlZGljdGVkX2J1cmdsYXJ5KQ0KICBlcnJvcl9yb2IgPC0gYWN0dWFsX3JvYmJlcnkgLSBwcmVkX3JvYmJlcnkNCiAgcGVyY2VudGFnZV9lcnJvcl9yb2IgPC0gZXJyb3Jfcm9iIC8gcHJlZF9yb2JiZXJ5IA0KICANCiAgI2NyZWF0ZSBvdXIgb3V0cHV0IGRhdGFmcmFtZSBhbmQgcmV0dXJuIGl0DQogIA0KICBtc29hX2Vycm9yX3RpYmJsZSA8LSB0aWJibGUoDQogICAgTVNPQTExQ0QgPSBtc29hTmFtZSwgDQogICAgYnVyZ2xhcnlBY3R1YWw9IGFjdHVhbF9idXJnbGFyeSwNCiAgICBidXJnbGFyeVByZWRpY3RlZD0gcHJlZF9idXJnbGFyeSwNCiAgICBidXJnbGFyeUVycm9yPSBlcnJvcl9idXJnLA0KICAgIGJ1cmdsYXJ5UGVyY2VudEVycm9yID0gcGVyY2VudGFnZV9lcnJvcl9idXJnLA0KICAgIHJvYmJlcnlBY3R1YWw9IGFjdHVhbF9yb2JiZXJ5LA0KICAgIHJvYmJlcnlQcmVkaWN0ZWQ9IHByZWRfcm9iYmVyeSwNCiAgICByb2JiZXJ5RXJyb3I9IGVycm9yX3JvYiwNCiAgICByb2JiZXJ5UGVyY2VudEVycm9yPXBlcmNlbnRhZ2VfZXJyb3Jfcm9iDQogICAgKQ0KDQogIHJldHVybihtc29hX2Vycm9yX3RpYmJsZSkNCn0NCmBgYA0KDQpNZXNzeSBhbmQgaGFja3ksIGJ1dCB0aGVvcmV0aWNhbGx5IGZ1bmN0aW9uYWwhICBOb3cgZm9yIHRoZSBsb25nIGJpdCAtIGxldCdzIGxvb3Agb3ZlciBhbGwgb3VyIE1TT0FzLCBhbmQgZ2V0IG91ciBhZ2dyZWdhdGUgZXJyb3IgZGF0YWZyYW1lLg0KDQoNCmBgYHtyfQ0KZm9yIChtc29hIGluIHVuaXF1ZShlbXB0eV9kZiRNU09BMTFDRCkpew0KICBpdGVyYXRlZF9tc29hX2RmIDwtIGNhbGN1bGF0ZV9lcnJvcihtc29hKQ0KICBtc29hX2Vycm9yX3RpYmJsZSA8LSByYmluZChtc29hX2Vycm9yX3RpYmJsZSwgaXRlcmF0ZWRfbXNvYV9kZikNCiAgDQp9DQoNCmBgYA0KYGBge3J9DQptc29hX2Vycm9yX3RpYmJsZQ0KYGBgDQoNCmBgYHtyfQ0Kd3JpdGVfY3N2KG1zb2FfZXJyb3JfdGliYmxlLCAibXNvYV9lcnJvcl90YWJsZS5jc3YiKQ0KYGBgDQoNCmBgYHtyfQ0KbXNvYV9lcnJvcl90aWJibGUNCmBgYA0KDQpgYGB7cn0NCm1zb2FfZXJyb3JfdGliYmxlWywyOjldIDwtIGxhcHBseShtc29hX2Vycm9yX3RpYmJsZVssMjo5XSwgYXMubnVtZXJpYykNCmBgYA0KYGBge3J9DQptc29hX2Vycm9yX3RpYmJsZSA8LSBtc29hX2Vycm9yX3RpYmJsZVsyOjk4MCwgXQ0KbXNvYV9lcnJvcl90aWJibGUNCmBgYA0KDQoNCkxldCdzIGFsc28gY2FsY3VsYXRlIHRoZSAiUmVsYXRpdmUgUGVyY2VudGFnZSBEaWZmZXJlbmNlIiAoUlBEKSBvZiBvdXIgZXN0aW1hdGVzLiANCg0KYGBge3J9DQptc29hX2Vycm9yX3RpYmJsZSRSUERCdXJnbGFyeSA8LSAyKigobXNvYV9lcnJvcl90aWJibGUkYnVyZ2xhcnlQcmVkaWN0ZWQgLSBtc29hX2Vycm9yX3RpYmJsZSRidXJnbGFyeUFjdHVhbCkvKGFicyhtc29hX2Vycm9yX3RpYmJsZSRidXJnbGFyeVByZWRpY3RlZCkgKyBhYnMobXNvYV9lcnJvcl90aWJibGUkYnVyZ2xhcnlBY3R1YWwpKSkNCg0KbXNvYV9lcnJvcl90aWJibGUkUlBEUm9iYmVyeSA8LSAyKigobXNvYV9lcnJvcl90aWJibGUkcm9iYmVyeVByZWRpY3RlZCAtIG1zb2FfZXJyb3JfdGliYmxlJHJvYmJlcnlBY3R1YWwpLyhhYnMobXNvYV9lcnJvcl90aWJibGUkcm9iYmVyeVByZWRpY3RlZCkgKyBhYnMobXNvYV9lcnJvcl90aWJibGUkcm9iYmVyeUFjdHVhbCkpKQ0KYGBgDQoNCk5vdywgbGV0J3MgbGluayBhbGwgdGhlc2UgYmFjayB0byBvdXIgb3JpZ2luYWwgZ2VvZ3JhcGhpYyBkYXRhZnJhbWUuDQoNCmBgYHtyfQ0KbHNvYV9ib3JkZXJzIDwtIHN0X3JlYWQoIm1zb2FfYm9yZGVycy9NU09BXzIwMTFfTG9uZG9uX2dlbl9NSFcudGFiIiwgY3JzPTI3NzAwKQ0KbHNvYV9ib3JkZXJzDQoNCmBgYA0KDQoNCmBgYHtyfQ0KZ2VvZ3JhcGhpY19lcnJvcl9tYXAgPC0gbGVmdF9qb2luKGxzb2FfYm9yZGVycywgbXNvYV9lcnJvcl90aWJibGUsIGJ5ID0gIk1TT0ExMUNEIikNCmdlb2dyYXBoaWNfZXJyb3JfbWFwDQpgYGANCkxldCdzIG1hcCBib3RoIG9mIHRoZXNlIG1ldHJpY3MsIGFuZCBzZWUgd2hhdCBpdCBsb29rcyBsaWtlLg0KDQpgYGB7cn0NCiMgbWFwDQoNCmJ1cmdfbWFwIDwtIHRtX3NoYXBlKGdlb2dyYXBoaWNfZXJyb3JfbWFwKSArDQogIHRtX2ZpbGwoY29sID0gIlJQRFJvYmJlcnkiLCB0aXRsZSA9ICJSb2JiZXJ5IFJlbGF0aXZlIEVycm9yIikNCg0Kcm9iX21hcCA8LXRtX3NoYXBlKGdlb2dyYXBoaWNfZXJyb3JfbWFwKSArDQogIHRtX2ZpbGwoY29sID0gIlJQREJ1cmdsYXJ5IiwgdGl0bGUgPSAiQnVyZ2xhcnkgUmVsYXRpdmUgRXJyb3IiKQ0KDQp0bWFwX2FycmFuZ2UoYnVyZ19tYXAsIHJvYl9tYXApDQoNCg0KYGBgDQpBcyBhIGZpbmFsIHBhcnQgb2YgdGhpcyBwcm9qZWN0LCBJJ20gZ29pbmcgdG8gZXhwbG9yZSBzb21lIGdlb2dyYXBoaWMgbW9kZWxsaW5nLiBMZXQncyBzdGFydCB3aXRoIGxpbmtpbmcgb3VyIGN1cnJlbnQgZGF0YSB3aXRoIHRoZSBMb25kb24gTU9QQUMgTVNPQSBBdGxhcywgd2hpY2ggc2hvdWxkIHByb3ZpZGUgYSB3aG9sZSBidW5jaCBvZiB1c2VmdWwgZGVtb2dyYXBoaWMgYW5kIGVjb25vbWljIGRhdGEuICBJJ3ZlIHNsaWdodGx5IG1vZGlmaWVkIGl0IGluIEV4Y2VsIHRvIGdldCByaWQgb2YgdGhlIHdlaXJkIGhlYWRlciBzdHJ1Y3R1cmUuDQoNCmBgYHtyfQ0KbGlicmFyeShyZWFkeGwpDQptc29hX2F0bGFzIDwtIHJlYWRfZXhjZWwoIm1zb2FfYXRsYXMvbXNvYS1kYXRhLnhscyIpDQptc29hX2F0bGFzDQpgYGANCg0KTGV0J3MgZG8gb25lIGxhc3Qgc3BhdGlhbCBqb2luIHRvIGJyaW5nIGFsbCB0aGVzZSB0aGluZ3MgdG9nZXRoZXINCg0KYGBge3J9DQpnZW9ncmFwaGljX21zb2FfbWF0cml4IDwtIGxlZnRfam9pbihnZW9ncmFwaGljX2Vycm9yX21hcCwgbXNvYV9hdGxhcywgYnkgPSAiTVNPQTExQ0QiKQ0KDQpgYGANCg0KTGV0J3MgcHJvdmlkZSBhIHRpYmxlIHZlcnNpb24gYXMgd2VsbCBmb3IgY2xlYXIgYW5hbHlzaXMNCg0KYGBge3J9DQptc29hX21hdHJpeF90YmwgPC0gYXNfdGliYmxlKGdlb2dyYXBoaWNfbXNvYV9tYXRyaXgpDQptc29hX21hdHJpeF90YmwNCmBgYA0KTGV0J3MgbG9vayBhdCBob3cgY29ycmVsYXRlZCBvdXIgZmFjdG9ycyBhcmUNCg0KYGBge3J9DQpsaWJyYXJ5KGNvcnJyKQ0KY29ycl9kZiA8LSBjb3JyZWxhdGUobXNvYV9tYXRyaXhfdGJsLCBxdWlldCA9IFRSVUUpDQpjb3JyX2RmDQpgYGANCg0KQW5ub3lpbmdseSwgdW5saWtlIFBhbmRhcywgUiB0aHJvd3MgdXAgZXJyb3JzIGhlcmUgKHdoaWxlIFB5dGhvbiBpbXBsaWNpdGx5IGdldHMgcmlkIG9mIG5vbi1udW1lcmljYWwgY29sdW1ucyAtIGxldCdzIGNsZWFuIGl0IHVwDQoNCmBgYHtyfQ0KbXNvYV9tYXRyaXhfbnVtZXJpYyA8LWRwbHlyOjpzZWxlY3RfaWYobXNvYV9tYXRyaXhfdGJsLCBpcy5udW1lcmljKQ0KbXNvYV9tYXRyaXhfbnVtZXJpYw0KYGBgDQoNCmBgYHtyfQ0KY29ycl9kZiA8LSBjb3JyZWxhdGUoZHBseXI6OnNlbGVjdF9pZihtc29hX21hdHJpeF90YmwsIGlzLm51bWVyaWMpLCBxdWlldCA9IFRSVUUpDQpjb3JyX2RmDQoNCg0KYGBgDQpMZXQncyBub3cgbG9vayBmb3IgY29ycmVsYXRlcyBvZiBvdXIgZXJyb3IgcmF0ZSBmb3IgYnVyZ2xhcnkgYW5kIHJvYmJlcnkNCg0KYGBge3J9DQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCg0KDQpkcGx5cjo6c2VsZWN0KGNvcnJfZGZbb3JkZXIoY29ycl9kZiRSUERSb2JiZXJ5KSxdICwgdGVybSwgUlBEUm9iYmVyeSkNCg0KYGBgDQoNCg0KYGBge3J9DQpkcGx5cjo6c2VsZWN0KGNvcnJfZGZbb3JkZXIoY29ycl9kZiRSUERCdXJnbGFyeSksXSAsIHRlcm0sIFJQREJ1cmdsYXJ5KQ0KYGBgDQpUaGVyZSBhcmUgdmVyeSBmZXcgZGVjZW50IGNvcnJlbGF0ZXMgaW4gdGhlIHJvYmJlcnkgZGF0YSAtIGV2ZXJ5dGhpbmcgaXMgYSBiaXQgb2YgYSBtZXNzLiAgVGhhdCdzIG5vdCB0aGUgY2FzZSBpbiB0aGUgYnVyZ2xhcnkgZGF0YSBob3dldmVyOiB3ZSBtaWdodCBiZSBhYmxlIHRvIGRvIHNvbWUgbW9kZWxpbmcgaGVyZS4gR2VuZXJhbCBkZXByaXZhdGlvbiBpbmRpY2F0b3JzIHN0YW5kIG91dCB2ZXJ5IHNoYXJwbHksIGNvcnJlbGF0ZWQgdG8gZXRobmljaXR5Lg0KDQpJJ2Qgbm9ybWFsbHkgc3RhcnQgZXhwbG9yaW5nIHNwYXRpYWwgbW9kZWxzIGFuZCB3ZWlnaHRzLCBidXQgSSB0aGluayB0aGF0J3MgYSBsaXR0bGUgb3V0c2lkZSB0aGUgc2NvcGUgb2YgdGhpcyBmaXJzdCBwcm9qZWN0LiAgSW5zdGVhZCwgbGV0J3MgZ2V0IHN0cmFpZ2h0IHRvIG1vZGVsaW5nLg0KDQpgYGB7cn0NCmxvZyhtc29hX21hdHJpeF9udW1lcmljWyJJbmNvbWUgRGVwcml2YXRpb24gKDIwMTApICUgbGl2aW5nIGluIGluY29tZSBkZXByaXZlZCBob3VzZWhvbGRzIHJlbGlhbnQgb24gbWVhbnMgdGVzdGVkIGJlbmVmaXQiXSkNCmBgYA0KYGBge3J9DQptc29hX2J1cmdsYXJ5X2NvcHkgPC0gbXNvYV9tYXRyaXhfbnVtZXJpYw0KDQpuYW1lcyhtc29hX2J1cmdsYXJ5X2NvcHkpW25hbWVzKG1zb2FfYnVyZ2xhcnlfY29weSkgPT0gIkluY29tZSBEZXByaXZhdGlvbiAoMjAxMCkgJSBsaXZpbmcgaW4gaW5jb21lIGRlcHJpdmVkIGhvdXNlaG9sZHMgcmVsaWFudCBvbiBtZWFucyB0ZXN0ZWQgYmVuZWZpdCJdIDwtICJoaFBlcmNlbnRCZW5lZml0Ig0KDQpuYW1lcyhtc29hX2J1cmdsYXJ5X2NvcHkpW25hbWVzKG1zb2FfYnVyZ2xhcnlfY29weSkgPT0gIkxvbmUgUGFyZW50cyAoMjAxMSBDZW5zdXMpIExvbmUgcGFyZW50cyBub3QgaW4gZW1wbG95bWVudCJdIDwtICJVbmVtcExvbmVQYXJlbnRzIg0KDQpuYW1lcyhtc29hX2J1cmdsYXJ5X2NvcHkpW25hbWVzKG1zb2FfYnVyZ2xhcnlfY29weSkgPT0gIkV0aG5pYyBHcm91cCAoMjAxMSBDZW5zdXMpIEJsYWNrL0FmcmljYW4vQ2FyaWJiZWFuL0JsYWNrIEJyaXRpc2ggKCUpIl0gPC0gImJsYWNrUGVyY2VudCINCg0KbmFtZXMobXNvYV9idXJnbGFyeV9jb3B5KVtuYW1lcyhtc29hX2J1cmdsYXJ5X2NvcHkpID09ICJIZWFsdGggKDIwMTEgQ2Vuc3VzKSBCYWQgaGVhbHRoICglKSJdIDwtICJwZXJjZW50QmFkSGVhbHRoIg0KDQpuYW1lcyhtc29hX2J1cmdsYXJ5X2NvcHkpW25hbWVzKG1zb2FfYnVyZ2xhcnlfY29weSkgPT0gIlRlbnVyZSAoMjAxMSkgT3duZWQ6IE93bmVkIG91dHJpZ2h0Il0gPC0gIkhvdXNlT3duZWQiDQoNCmBgYA0KDQoNCmBgYHtyfQ0KZmVhdHVyZV9kZiA8LSBkcGx5cjo6c2VsZWN0KG1zb2FfYnVyZ2xhcnlfY29weSwgUlBEQnVyZ2xhcnksIGhoUGVyY2VudEJlbmVmaXQsIFVuZW1wTG9uZVBhcmVudHMsIGJsYWNrUGVyY2VudCwgcGVyY2VudEJhZEhlYWx0aCwgSG91c2VPd25lZCkNCmZlYXR1cmVfZGYNCmBgYA0KV2UgaGF2ZSBhIGZldyBOQS4gIExldCdzIGdldCByaWQgb2YgdGhlbQ0KDQpgYGB7cn0NCmZlYXR1cmVfZGYgPC0gZHJvcF9uYShmZWF0dXJlX2RmLCBSUERCdXJnbGFyeSkNCmZlYXR1cmVfZGYNCmBgYA0KDQoNCmBgYHtyfQ0KIGNvbG5hbWVzKGZlYXR1cmVfZGYpDQpgYGANCmBgYHtyfQ0KY29sU3Vtcyhpcy5uYShmZWF0dXJlX2RmKSkNCmBgYA0KDQpgYGB7cn0NCnBhaXJzKGZlYXR1cmVfZGYpDQoNCmBgYA0KDQpXZSBhcmUgZ29pbmcgdG8gd2FudCB0byBwZXJmb3JtIGEgbG9nIHRyYW5zZm9ybS4gIEJlY2F1c2Ugb3VyIFJQRCBoYXMgbmVnYXRpdmUgdmFsdWVzLCB3ZSdsbCBuZWVkIHRvIGFkZCBhIGNvbnN0YW50IGJlZm9yZSAtIGluIHRoaXNlIGNhc2UsIDMgKHNvIGV2ZXJ5dGhpbmcgaXMgcG9zaXRpdmUpDQpgYGB7cn0NCmZlYXR1cmVfZGYkUlBEQnVyZ2xhcnlUcmFuZm9ybSA8LSBmZWF0dXJlX2RmJFJQREJ1cmdsYXJ5ICsgMw0KZmVhdHVyZV9kZg0KYGBgDQoNCg0KDQpOb3csIGxldCdzIHByb2R1Y2Ugb3VyIGxvZyB0cmFuc2Zvcm0gY29sdW1ucw0KDQpgYGB7cn0NCmZvciAoY29sIGluIGNvbG5hbWVzKGZlYXR1cmVfZGYpKXsNCiAgbmV3X25hbWUgPC0gcGFzdGUoImxvZ18iLCBjb2wsIHNlcCA9ICIiKQ0KICBmZWF0dXJlX2RmW25ld19uYW1lXSA8LSBsb2coZmVhdHVyZV9kZltjb2xdKQ0KfQ0KYGBgDQpgYGB7cn0NCmZlYXRfdHJhbnNmb3JtX2RmIDwtIGZlYXR1cmVfZGZbLDk6MTRdDQpmZWF0X3RyYW5zZm9ybV9kZg0KYGBgDQoNCmBgYHtyfQ0KcGFpcnMoZmVhdF90cmFuc2Zvcm1fZGYpDQpgYGANCg0KYGBge3J9DQpjb3JyZWxhdGUoZmVhdF90cmFuc2Zvcm1fZGYpDQpgYGANCg0KYGBge3J9DQptb2RfYnVyZ2xhcnkgPC0gbG0obG9nX1JQREJ1cmdsYXJ5VHJhbmZvcm0gfiBsb2dfYmxhY2tQZXJjZW50ICsgbG9nX2hoUGVyY2VudEJlbmVmaXQgLCBkYXRhID0gZmVhdF90cmFuc2Zvcm1fZGYpDQpzdW1tYXJ5KG1vZF9idXJnbGFyeSkNCmBgYA0KDQpgYGB7cn0NCm1vZF9idXJnbGFyeSA8LSBsbShsb2dfUlBEQnVyZ2xhcnlUcmFuZm9ybSB+IGxvZ19ibGFja1BlcmNlbnQgLCBkYXRhID0gZmVhdF90cmFuc2Zvcm1fZGYpDQpzdW1tYXJ5KG1vZF9idXJnbGFyeSkNCmBgYA0KDQpTbywgd2UgaGF2ZSBzaWduaWZpY2FuY2UuICBJdCBsb29rcyBsaWtlIHRoZSBtYWluIGRyaXZlciBhcmUgZ2VuZXJhbCBkZXByaXZhdGlvbiBpbmRpY2F0b3JzLCBidXQgZHVlIHRvIGNvcnJlbGF0aW9uLCB3ZSdyZSBzdHJ1Z2dsaW5nIHRvIGdldCBhbnkgbW9yZSBkZXRhaWxlZC4gIExldCdzIHVzZSBSYW5kb20gRm9yZXN0cyB0byB0cnkgYW5kIGRpZyBpbnRvIHRoaXMgYXMgYml0IGZ1cnRoZXIuDQoNCmBgYHtyfQ0KbXNvYV9tYXRyaXhfbnVtZXJpYw0KYGBgDQpPbmNlIGFnYWluLCBsZXQncyBnZXQgcmlkIG9mIE5BLiAgVGhpcyB0aW1lLCBsZXQncyBkbyBvdXIgUlBELCBhbmQgdGhlbiBhbnkgY29sdW1ucw0KYGBge3J9DQpyZl9tc29hX21hdHJpeCA8LSBkcm9wX25hKG1zb2FfbWF0cml4X251bWVyaWMsIFJQREJ1cmdsYXJ5KQ0KDQpyZl9tc29hX21hdHJpeA0KYGBgDQoNCmBgYHtyfQ0KY2xlYW5fcmZfbWF0cml4IDwtIHJmX21zb2FfbWF0cml4WyAsIGNvbFN1bXMoaXMubmEocmZfbXNvYV9tYXRyaXgpKSA9PSAwXQ0KY2xlYW5fcmZfbWF0cml4DQpgYGANClJpZ2h0LCB3ZSd2ZSBub3cgZ290IG91ciBkYXRhIGdvb2QgdG8gZ28sIHdpdGggbm8gTkFzLiANCmh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS9yYW5kb20tZm9yZXN0LWluLXItZjY2YWRmODBlYzkNCg0KYGBge3J9DQoNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KcmVxdWlyZShjYVRvb2xzKQ0KbGlicmFyeShjYXJldCkNCmBgYA0KYGBge3J9DQpjbGVhbl9yZl9tYXRyaXgNCmBgYA0KDQoNCmBgYHtyfQ0KZHJvcDwtIGMoImJ1cmdsYXJ5QWN0dWFsIiwiYnVyZ2xhcnlFcnJvciIsImJ1cmdsYXJ5UGVyY2VudEVycm9yIiwiYnVyZ2xhcnlQcmVkaWN0ZWQiLCJyb2JiZXJ5QWN0dWFsIiwicm9iYmVyeVByZWRpY3RlZCIsInJvYmJlcnlFcnJvciIsInJvYmJlcnlQZXJjZW50RXJyb3IiLCJSUERSb2JiZXJ5IikNCmRhdGE8LSBjbGVhbl9yZl9tYXRyaXhbLCEobmFtZXMoY2xlYW5fcmZfbWF0cml4KSAlaW4lIGRyb3ApXQ0KZGF0YQ0KYGBgDQoNCg0KYGBge3J9DQoNCnNhbXBsZSA9IHNhbXBsZS5zcGxpdChkYXRhJFJQREJ1cmdsYXJ5LCBTcGxpdFJhdGlvID0gMC43NSkNCnRyYWluID0gc3Vic2V0KGRhdGEsIHNhbXBsZSA9PSBUUlVFKQ0KdGVzdCAgPSBzdWJzZXQoZGF0YSwgc2FtcGxlID09IEZBTFNFKQ0KZGltKHRyYWluKQ0KZGltKHRlc3QpDQpgYGANCmBgYHtyfQ0KcmYgPC0gcmFuZG9tRm9yZXN0KA0KICBSUERCdXJnbGFyeSB+IC4sDQogIGRhdGE9dHJhaW4NCikNCmBgYA0KQW5ub3lpbmdseSwgYmVjYXVzZSBteSBjb2x1bW5zIGhhdmUgd2hpdGUgc3BhY2VzLCBSIGRvZXNuJ3QgbGlrZSBpdC4NCg0KYGBge3J9DQpuYW1lcyhjbGVhbl9yZl9tYXRyaXgpPC1tYWtlLm5hbWVzKG5hbWVzKGNsZWFuX3JmX21hdHJpeCksdW5pcXVlID0gVFJVRSkNCg0KYGBgDQoNCg0KYGBge3J9DQoNCmRyb3A8LSBjKCJidXJnbGFyeUFjdHVhbCIsImJ1cmdsYXJ5RXJyb3IiLCJidXJnbGFyeVBlcmNlbnRFcnJvciIsImJ1cmdsYXJ5UHJlZGljdGVkIiwicm9iYmVyeUFjdHVhbCIsInJvYmJlcnlQcmVkaWN0ZWQiLCJyb2JiZXJ5RXJyb3IiLCJyb2JiZXJ5UGVyY2VudEVycm9yIiwiUlBEUm9iYmVyeSIpDQpkYXRhPC0gY2xlYW5fcmZfbWF0cml4WywhKG5hbWVzKGNsZWFuX3JmX21hdHJpeCkgJWluJSBkcm9wKV0NCg0KDQpuYW1lcyhkYXRhKTwtIG1ha2UubmFtZXMobmFtZXMoZGF0YSksdW5pcXVlID0gVFJVRSkNCmRhdGENCmBgYA0KDQoNCmBgYHtyfQ0Kc2FtcGxlID0gc2FtcGxlLnNwbGl0KGRhdGEkUlBEQnVyZ2xhcnksIFNwbGl0UmF0aW8gPSAwLjc1KQ0KdHJhaW4gPSBzdWJzZXQoZGF0YSwgc2FtcGxlID09IFRSVUUpDQp0ZXN0ICA9IHN1YnNldChkYXRhLCBzYW1wbGUgPT0gRkFMU0UpDQoNCg0KcmYgPC0gcmFuZG9tRm9yZXN0KA0KICBSUERCdXJnbGFyeSB+IC4sDQogIGRhdGE9dHJhaW4sIA0KICBpbXBvcnRhbmNlPVRSVUUNCikNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkocmYpDQpgYGANCmBgYHtyfQ0KbGlicmFyeShEQUxFWCkNCmBgYA0KYGBge3J9DQoNCnJmX2V4cGxhaW5lciA8LSBleHBsYWluKHJmLCBkYXRhPXRyYWluLCB5PSB0cmFpbiRSUERCdXJnbGFyeSkNCmBgYA0KDQpgYGB7cn0NCnJmX3BlcmYgPC0gbW9kZWxfcGVyZm9ybWFuY2UocmZfZXhwbGFpbmVyKQ0KcmZfcGVyZg0KYGBgDQpgYGB7cn0NCmhlbHAodmFyaWFibGVfaW1wb3J0YW5jZSkNCg0KDQpgYGANCg0KYGBge3J9DQp2YXJfaW1wb3J0YW5jZV9yYW5nZXJfYWZ0ZXJfdmkgPC0gdmFyaWFibGVfaW1wb3J0YW5jZSgNCiAgcmZfZXhwbGFpbmVyLA0KICBsb3NzX2Z1bmN0aW9uID0gbG9zc19yb290X21lYW5fc3F1YXJlLA0KICBCID0gMTAsDQogIHR5cGUgPSAicmF3IikNCg0KcGxvdCh2YXJfaW1wb3J0YW5jZV9yYW5nZXJfYWZ0ZXJfdmkpDQpgYGANCg0KYGBge3J9DQp2YXJfaW1wb3J0YW5jZV9yYW5nZXJfYWZ0ZXJfdmkNCmBgYA0KDQpgYGB7cn0NCm1vZGVsX3BhcnRzIDwtbW9kZWxfcGFydHMocmZfZXhwbGFpbmVyKQ0KbW9kZWxfcGFydHMNCg0KYGBgDQpgYGB7cn0NCnBsb3QobW9kZWxfcGFydHMsIG1heF92YXJzPTEwKQ0KYGBgDQoNCmBgYHtyfQ0KcGRwIDwtIG1vZGVsX3Byb2ZpbGUocmZfZXhwbGFpbmVyKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChwZHAsIHZhcmlhYmxlcz0iQ29tRXN0UmVzIikNCg0KYGBgDQoNCmBgYHtyfQ0KcGxvdChwZHAsIHZhcmlhYmxlcz0gIkVjb25vbWljLkFjdGl2aXR5Li4yMDExLkNlbnN1cy4uVW5lbXBsb3ltZW50LlJhdGUiKQ0KDQpgYGANCg0KDQpgYGB7cn0NCnBsb3QocGRwLCB2YXJpYWJsZXM9KCJDb21Fc3RSZXMiLCAiRWNvbm9taWMuQWN0aXZpdHkuLjIwMTEuQ2Vuc3VzLi5VbmVtcGxveW1lbnQuUmF0ZSIpKQ0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0KDQpkcm9wPC0gYygiUlBEQnVyZ2xhcnkiKQ0KdGVzdDwtIHRlc3RbLCEobmFtZXModGVzdCkgJWluJSBkcm9wKV0NCg0KDQpwcmVkID0gcHJlZGljdChyZiwgbmV3ZGF0YT10ZXN0KQ0KDQoNCmBgYA0KYGBge3J9DQp0ZXN0JFByZWRpY3Rpb25zPC1wcmVkDQp0ZXN0DQpgYGANCg0KYGBge3J9DQoNCg0Kc3FydChzdW0ocHJlZCAtIHRlc3QkUlBEQnVyZ2xhcnkpXjIpICNSTVNFDQoNCg0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KHRlc3QsIGFlcyh4PVJQREJ1cmdsYXJ5LCB5PVByZWRpY3Rpb25zKSkgKyBnZW9tX3BvaW50KCkNCg0KYGBgDQoNCg0K